commit 030892f013a892284e973823189b0f6061366e31 Author: fabio-4 <65510654+fabio-4@users.noreply.github.com> Date: Mon Jun 29 23:37:21 2020 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f476a9 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Sharebounds: OCR - Keyboard + +![Header](screenshots/header.png) + +(Built in 2018) + +## Screenshots + +

+ + +

+

+ + +

diff --git a/build.gradle b/build.gradle new file mode 100755 index 0000000..4736554 --- /dev/null +++ b/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'com.android.application' +apply plugin: 'com.google.gms.oss.licenses.plugin' + +android { + compileSdkVersion 27 + buildToolsVersion "27.0.3" + defaultConfig { + applicationId "com.sharebounds.sharebounds" + minSdkVersion 16 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + vectorDrawables.useSupportLibrary = true + } + buildTypes { + debug { + shrinkResources false + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + release { + shrinkResources true + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:design:27.1.0' + implementation 'com.android.support:support-v4:27.1.0' + implementation 'com.android.support:appcompat-v7:27.1.0' + implementation 'com.android.support:preference-v14:27.1.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + implementation 'com.squareup.picasso:picasso:2.71828' + implementation 'com.google.android.gms:play-services-vision:12.0.1' + implementation 'com.google.android.gms:play-services-oss-licenses:12.0.1' +} diff --git a/screenshots/1.png b/screenshots/1.png new file mode 100755 index 0000000..7d2ab4c Binary files /dev/null and b/screenshots/1.png differ diff --git a/screenshots/2.png b/screenshots/2.png new file mode 100755 index 0000000..8e6f8a9 Binary files /dev/null and b/screenshots/2.png differ diff --git a/screenshots/3.png b/screenshots/3.png new file mode 100755 index 0000000..7ed3d6d Binary files /dev/null and b/screenshots/3.png differ diff --git a/screenshots/4.png b/screenshots/4.png new file mode 100755 index 0000000..1ebf2e9 Binary files /dev/null and b/screenshots/4.png differ diff --git a/screenshots/header.png b/screenshots/header.png new file mode 100755 index 0000000..60c8aab Binary files /dev/null and b/screenshots/header.png differ diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml new file mode 100755 index 0000000..a19eba3 --- /dev/null +++ b/src/main/AndroidManifest.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/ic_launcher-web.png b/src/main/ic_launcher-web.png new file mode 100755 index 0000000..9e1a229 Binary files /dev/null and b/src/main/ic_launcher-web.png differ diff --git a/src/main/java/com/sharebounds/sharebounds/AppSettings.java b/src/main/java/com/sharebounds/sharebounds/AppSettings.java new file mode 100755 index 0000000..552dafb --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/AppSettings.java @@ -0,0 +1,97 @@ +package com.sharebounds.sharebounds; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.v7.preference.PreferenceManager; + +public final class AppSettings { + + public enum LaunchDialog {Tutorial, Rate, None} + + private static AppSettings sInstance; + private SharedPreferences mSharedPreferences; + + private static final String DESIGN_LIST_KEY = "design_colors_list"; + private static final String KEYBOARD_LIST = "keyboard_globe_list"; + private static final String APP_PRO_VERSION = "pro_sharebounds_msg"; + private static final String FLASH_MODE = "flash_mode"; + private static final String IS_FULL_SCREEN = "is_full_screen"; + private static final String KEYBOARD_SOUND = "keyboard_sound"; + private static final String KEYBOARD_VIBRATION = "keyboard_vibration"; + private static final String TERMS_ACCEPTED = "terms_accepted"; + + private static final int SHOW_RATE_COUNT = 15; + private static final String RATE_COUNT = "rate_count"; + + private AppSettings(Context context) { + this.mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + } + + // ONLY PASS APPLICATION CONTEXT + public static AppSettings getInstance(Context context) { + if (sInstance == null) { + sInstance = new AppSettings(context); + } + return sInstance; + } + + public boolean getIsFullScreen() { + return mSharedPreferences.getBoolean(IS_FULL_SCREEN, false); + } + + public void setIsFullScreen(boolean bool) { + mSharedPreferences.edit().putBoolean(IS_FULL_SCREEN, bool).apply(); + } + + public int getFlashMode() { + return mSharedPreferences.getInt(FLASH_MODE, 0); + } + + public void setFlashMode(int i) { + mSharedPreferences.edit().putInt(FLASH_MODE, i).apply(); + } + + public int getTheme() { + return Integer.parseInt(mSharedPreferences.getString(DESIGN_LIST_KEY, "0")); + } + + public int getGlobeFunction() { + String value = mSharedPreferences.getString(KEYBOARD_LIST, "0"); + return Integer.parseInt(value); + } + + public boolean getKeyboardSound() { + return mSharedPreferences.getBoolean(KEYBOARD_SOUND, true); + } + + public boolean getKeyboardVibration() { + return mSharedPreferences.getBoolean(KEYBOARD_VIBRATION, true); + } + + public boolean getAppPro() { + // also check store cache, w/o in app: add text + return mSharedPreferences.getBoolean(APP_PRO_VERSION, false); + } + + public LaunchDialog getLaunchDialog() { + int count = mSharedPreferences.getInt(RATE_COUNT, 0); + if (count == -1 || count > SHOW_RATE_COUNT) return LaunchDialog.None; + + newRateDialogCount(count+1); + if (count == 0) return LaunchDialog.Tutorial; + if (count == SHOW_RATE_COUNT) return LaunchDialog.Rate; + return LaunchDialog.None; + } + + public void newRateDialogCount(int newCount) { + mSharedPreferences.edit().putInt(RATE_COUNT, newCount).apply(); + } + + public boolean getTermsAccepted() { + return mSharedPreferences.getBoolean(TERMS_ACCEPTED, false); + } + + public void setTermsAccepted(boolean bool) { + mSharedPreferences.edit().putBoolean(TERMS_ACCEPTED, bool).apply(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/BaseThemeImageButton.java b/src/main/java/com/sharebounds/sharebounds/BaseThemeImageButton.java new file mode 100755 index 0000000..0a42b72 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/BaseThemeImageButton.java @@ -0,0 +1,36 @@ +package com.sharebounds.sharebounds; + +import android.content.Context; +import android.graphics.Color; +import android.support.v7.widget.AppCompatImageButton; +import android.util.AttributeSet; + +public class BaseThemeImageButton extends AppCompatImageButton { + + public BaseThemeImageButton(Context context, AttributeSet set) { + super(context, set); + setupButton(context); + } + + private void setupButton(Context context) { + int theme = AppSettings.getInstance(context.getApplicationContext()).getTheme(); + setTheme(theme); + } + + public void setTheme(int theme) { + int tintColor; + int background; + switch (theme) { + case 1: + tintColor = 255; + background = R.drawable.round_button_dark; + break; + default: + tintColor = 255; + background = R.drawable.round_button; + } + this.setBackgroundResource(background); + this.setColorFilter(Color.argb(tintColor, tintColor, tintColor, tintColor)); + this.invalidate(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/OrientationListener.java b/src/main/java/com/sharebounds/sharebounds/OrientationListener.java new file mode 100755 index 0000000..ffbb94f --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/OrientationListener.java @@ -0,0 +1,101 @@ +package com.sharebounds.sharebounds; + +import android.content.Context; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.view.WindowManager; + +public class OrientationListener extends OrientationEventListener { + + public interface OrientationCallBack { + void onRotation(int oldRotation, int newRotation); + } + + private int mOldRotation = 0; + private OrientationCallBack mCallBack; + + private int mROT_0 = 0; + private int mROT_90 = 90; + private int mROT_180 = 180; + private int mROT_270 = 270; + + public OrientationListener(Context context, int rate, + OrientationCallBack callBack) { + super(context, rate); + mCallBack = callBack; + setup(context); + } + + public void setup(Context context) { + int origin = getDisplayRotation(context); + if (origin == 270) { + mROT_0 = 90; mROT_90 = 180; mROT_180 = 270; mROT_270 = 0; + } else if (origin == 90) { + mROT_0 = 270; mROT_90 = 0; mROT_180 = 90; mROT_270 = 180; + } else if (origin == 180) { + mROT_0 = 180; mROT_90 = 270; mROT_180 = 0; mROT_270 = 90; + } else { + mROT_0 = 0; mROT_90 = 90; mROT_180 = 180; mROT_270 = 270; + } + mOldRotation = 0; + } + + public void release() { + mCallBack = null; + } + + @Override + public void onOrientationChanged(int i) { + newOrientation(i); + } + + private void newOrientation(int i) { + int newRotation = mOldRotation; + + if (i == OrientationEventListener.ORIENTATION_UNKNOWN) return; + + if((i < 25 || i > 335)){ + newRotation = mROT_0; + } + else if( i > 155 && i < 205 ){ + newRotation = mROT_180; + } + else if(i > 65 && i < 115){ + newRotation = mROT_270; + } + else if(i > 245 && i < 295){ + newRotation = mROT_90; + } + + if (newRotation != mOldRotation) { + int animRotation = newRotation; + if ((newRotation - mOldRotation) == 270) { + animRotation = -90; + } else if ((newRotation - mOldRotation) == -270) { + animRotation = 360; + } + + if (mCallBack != null) mCallBack.onRotation(mOldRotation, animRotation); + mOldRotation = newRotation; + } + } + + public static int getDisplayRotation(Context context) { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + int rotation = Surface.ROTATION_0; + if (windowManager != null){ + rotation = windowManager.getDefaultDisplay().getRotation(); + } + switch (rotation) { + case Surface.ROTATION_0: + return 0; + case Surface.ROTATION_90: + return 90; + case Surface.ROTATION_180: + return 180; + case Surface.ROTATION_270: + return 270; + } + return 0; + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/PermissionUtils.java b/src/main/java/com/sharebounds/sharebounds/PermissionUtils.java new file mode 100755 index 0000000..1e3e05b --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/PermissionUtils.java @@ -0,0 +1,55 @@ +package com.sharebounds.sharebounds; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.widget.Toast; + +public class PermissionUtils { + + public enum Type { + Camera (Manifest.permission.CAMERA), + Storage (Manifest.permission.READ_EXTERNAL_STORAGE); + + private final String sPermission; + Type(String permission) { + sPermission = permission; + } + + String getPermissionType() { + return sPermission; + } + + static int getRequestCode(Type permission) { + return (permission == Type.Camera) ? CAMERA_REQUEST : STORAGE_REQUEST; + } + + public static Type getType(int requestCode) { + return (requestCode == CAMERA_REQUEST) ? Type.Camera : Type.Storage; + } + } + + public static final int CAMERA_REQUEST = 0; + public static final int STORAGE_REQUEST = 1; + + public static boolean getPermission(final Context context, Type permission) { + return (ContextCompat.checkSelfPermission(context, permission.getPermissionType()) + == PackageManager.PERMISSION_GRANTED); + } + + public static void requestPermission(Activity activity, Type permission) { + ActivityCompat.requestPermissions(activity, + new String[]{permission.getPermissionType()}, + Type.getRequestCode(permission)); + } + + public static void errorToast(Context context, Type permission) { + String error = (permission == Type.Camera) ? + context.getString(R.string.camera_permission_denied) + : context.getString(R.string.storage_permission_denied); + Toast.makeText(context, error, Toast.LENGTH_LONG).show(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/BitmapImageView.java b/src/main/java/com/sharebounds/sharebounds/camera/BitmapImageView.java new file mode 100755 index 0000000..78140f7 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/BitmapImageView.java @@ -0,0 +1,150 @@ +package com.sharebounds.sharebounds.camera; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.util.Pair; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.R; + +public class BitmapImageView extends AppCompatImageView { + + AnimatorSet animatorSet; + private RectF mAnimationRect; + private float mAnimationRectRadius; + private Paint mPaint = new Paint(); + private int animationFrames = 60; + + void animate(RectF rect) { + mAnimationRect = rect; + setStroke(Math.min(80, rect.height())); + animatorSet.start(); + } + + void endAnimation() { + if (animatorSet != null && animatorSet.isRunning()) + animatorSet.end(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mAnimationRect != null) { + canvas.drawRoundRect(mAnimationRect, mAnimationRectRadius, mAnimationRectRadius, mPaint); + } + } + + public BitmapImageView(Context context, AttributeSet attrs) { + super(context, attrs); + this.setScaleType(ScaleType.MATRIX); + int theme = AppSettings.getInstance(context.getApplicationContext()).getTheme(); + setTheme(theme); + setupAnimation(); + } + + @Override + public boolean performClick() { + return super.performClick(); + } + + private void setStroke(float width) { + mAnimationRectRadius = width / 8; + mPaint.setStrokeWidth(mAnimationRectRadius); + } + + void setTheme(int theme) { + int color; + switch (theme) { + case 1: + color = R.color.colorDarkPrimary; + break; + default: + color = R.color.colorPrimary; + } + mPaint.setColor(getResources().getColor(color)); + } + + private void setupAnimation() { + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setAntiAlias(true); + mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); + + ValueAnimator appearAnimator = ValueAnimator.ofInt(0, animationFrames); + appearAnimator.setDuration(150).setInterpolator(new DecelerateInterpolator()); + appearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + int value = (int) valueAnimator.getAnimatedValue(); + mPaint.setAlpha(255 * value / animationFrames); + invalidate(); + } + }); + + ValueAnimator disappearAnimator = ValueAnimator.ofInt(0, animationFrames); + disappearAnimator.setDuration(150).setInterpolator(new AccelerateInterpolator()); + disappearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + int value = (int) valueAnimator.getAnimatedValue(); + mPaint.setAlpha(255 * (animationFrames - (value / animationFrames))); + invalidate(); + } + }); + + animatorSet = new AnimatorSet(); + animatorSet.playSequentially(appearAnimator, disappearAnimator); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mAnimationRect = null; + invalidate(); + } + }); + } + + Matrix setImageViewBitmap(Bitmap rotImageBitmap, Pair sizeValues) { + Matrix fitMatrix = new Matrix(); + RectF imageRectF = new RectF(0, 0, rotImageBitmap.getWidth(), rotImageBitmap.getHeight()); + RectF viewRectF = new RectF(0, 0, sizeValues.first, sizeValues.second); + fitMatrix.setRectToRect(imageRectF, viewRectF, Matrix.ScaleToFit.CENTER); + this.setImageMatrix(fitMatrix); + + this.setImageBitmap(null); + this.setImageBitmap(rotImageBitmap); + invalidate(); + if (this.getAlpha() == 0.0f) { + this.animate().alpha(1.0f).setDuration(300).withStartAction(new Runnable() { + @Override + public void run() { + BitmapImageView.this.setVisibility(View.VISIBLE); + } + }); + } + return fitMatrix; + } + + void reset() { + this.animate().alpha(0.0f).setDuration(300).withEndAction(new Runnable() { + @Override + public void run() { + BitmapImageView.this.setVisibility(View.INVISIBLE); + } + }); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/BitmapUtils.java b/src/main/java/com/sharebounds/sharebounds/camera/BitmapUtils.java new file mode 100755 index 0000000..c0b34cb --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/BitmapUtils.java @@ -0,0 +1,90 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.R; + +import java.util.List; + +class BitmapUtils { + + static Bitmap convertToBitmap(byte[] bytes, ImageData imageData) { + final Bitmap convertedBitmap = BitmapUtils.convertToBitmap(bytes); + return BitmapUtils.rotateScale(convertedBitmap, imageData); + } + + private static Bitmap convertToBitmap(byte[] bytes) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inMutable = true; + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); + } + + private static Bitmap rotateScale(Bitmap bitmap, ImageData imageData) { + Float rotation = imageData.rotation + imageData.fixRotation; + float[] matrixValues = imageData.matrixValues; + boolean portrait = imageData.portrait; + + Matrix rotMatrix = new Matrix(); + rotMatrix.postRotate(rotation); + int newW, newH; + if (rotation % 180 != 0 || portrait) { + newW = (int) (bitmap.getWidth() / matrixValues[4]); + newH = (int) (bitmap.getHeight() / matrixValues[0]); + } else { + newW = (int) (bitmap.getWidth() / matrixValues[0]); + newH = (int) (bitmap.getHeight() / matrixValues[4]); + } + int x = (bitmap.getWidth() - newW) / 2; + int y = (bitmap.getHeight() - newH) / 2; + + Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, x, y, newW, newH, rotMatrix, true); + bitmap.recycle(); + return rotatedBitmap; + } + + static Bitmap mutableCopy(Bitmap bitmap) { + return bitmap.copy(Bitmap.Config.ARGB_8888, true); + } + + private Context mContext; + private Paint mPaint = new Paint(); + + BitmapUtils(Context context) { + mContext = context; + int theme = AppSettings.getInstance(context.getApplicationContext()).getTheme(); + setTheme(theme); + } + + void setTheme(int theme) { + int color; + switch (theme) { + case 1: + color = R.color.colorDarkAccent; + break; + default: + color = R.color.colorAccent; + } + mPaint.setColor(mContext.getResources().getColor(color)); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setAlpha(150); + mPaint.setAntiAlias(true); + mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); + } + + void drawImageRect(Bitmap imageBitmap, List lines) { + Canvas canvas = new Canvas(imageBitmap); + for (RectF line: lines) { + float round = line.height() / 8; + canvas.drawRoundRect(line, round, round, mPaint); + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/CameraAnimationView.java b/src/main/java/com/sharebounds/sharebounds/camera/CameraAnimationView.java new file mode 100755 index 0000000..643a1fc --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/CameraAnimationView.java @@ -0,0 +1,38 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; + +import com.sharebounds.sharebounds.R; + +class CameraAnimationView extends View { + + private Animation mShutterAnim; + + public CameraAnimationView(Context context, AttributeSet set) { + super(context, set); + setBackgroundColor(Color.WHITE); + setVisibility(View.INVISIBLE); + mShutterAnim = AnimationUtils.loadAnimation(context.getApplicationContext(), R.anim.shutter); + mShutterAnim.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + CameraAnimationView.this.setVisibility(View.VISIBLE); + } + @Override + public void onAnimationEnd(Animation animation) { + CameraAnimationView.this.setVisibility(View.INVISIBLE); + } + @Override + public void onAnimationRepeat(Animation animation) { } + }); + } + + void shutter() { + this.startAnimation(mShutterAnim); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/CameraGestures.java b/src/main/java/com/sharebounds/sharebounds/camera/CameraGestures.java new file mode 100755 index 0000000..e033cea --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/CameraGestures.java @@ -0,0 +1,48 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; + +class CameraGestures { + + interface GesturesCallBack { + void cameraZoom(float scale); + void cameraTap(); + } + + private GesturesCallBack mGesturesCallBack; + private ScaleGestureDetector mScaleGestureDetector; + private GestureDetector mTapGestureListener; + + CameraGestures(Context context, GesturesCallBack callBack) { + mGesturesCallBack = callBack; + mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener()); + mTapGestureListener = new GestureDetector(context, new TapGestureListener()); + } + + void onTouch(View view, MotionEvent motionEvent) { + mScaleGestureDetector.onTouchEvent(motionEvent); + mTapGestureListener.onTouchEvent(motionEvent); + if (motionEvent.getAction() == MotionEvent.ACTION_UP) view.performClick(); + } + + private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + @Override + public boolean onScale(ScaleGestureDetector detector) { + float scale = Math.max(0.1f, Math.min(detector.getScaleFactor(), 5.0f)); + mGesturesCallBack.cameraZoom(scale); + return true; + } + } + + private class TapGestureListener extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + mGesturesCallBack.cameraTap(); + return super.onSingleTapConfirmed(e); + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/CameraTextureView.java b/src/main/java/com/sharebounds/sharebounds/camera/CameraTextureView.java new file mode 100755 index 0000000..3a36e58 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/CameraTextureView.java @@ -0,0 +1,100 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.util.AttributeSet; +import android.view.Surface; +import android.view.TextureView; +import android.view.View; + +public class CameraTextureView extends TextureView { + + public CameraTextureView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + void reset() { + if (isAvailable()) { + Surface surface = null; + try { + surface = new Surface(getSurfaceTexture()); + if (surface.isValid()) { + Canvas canvas = surface.lockCanvas(null); + if (canvas != null) { + canvas.drawColor(Color.BLACK); + surface.unlockCanvasAndPost(canvas); + } + } + } catch (Surface.OutOfResourcesException e) { // + } finally { + if (surface != null) surface.release(); + } + } + } + + void visible(boolean bool) { + if (bool) { + this.animate().cancel(); + if (this.getAlpha() == 0.0f) this.animate().alpha(1.0f).setDuration(0) + .setStartDelay(300).withStartAction(new Runnable() { + @Override + public void run() { + CameraTextureView.this.setVisibility(View.VISIBLE); + } + }); + } + else if (this.getAlpha() == 1.0f) this.animate().alpha(0.0f).setDuration(0).setStartDelay(300) + .withEndAction(new Runnable() { + @Override + public void run() { + CameraTextureView.this.reset(); + CameraTextureView.this.setVisibility(View.INVISIBLE); + } + }); + } + + void scalePreview(double aspectRatio, int rotation) { + Matrix matrix = scaleMatrix(aspectRatio, rotation); + setTransform(matrix); + } + + Matrix scaleMatrix(double aspectRatio, int rotation) { + Matrix matrix = new Matrix(); + int textureWidth = getWidth(); + int textureHeight = getHeight(); + + if (rotation % 180 != 0) { + int tmp = textureWidth; + textureWidth = textureHeight; + textureHeight = tmp; + } + int newW, newH; + if (textureHeight > (int)(textureWidth * aspectRatio)) { + newW = (int)(textureHeight / aspectRatio); + newH = textureHeight; + } else { + newW = textureWidth; + newH = (int)(textureWidth * aspectRatio); + } + float scaleY = (float) newW / (float) textureWidth; + float scaleX = (float) newH / (float) textureHeight; + int transX = (textureHeight - (int) (textureHeight * scaleX)) / 2; + int transY = (textureWidth - (int) (textureWidth * scaleY)) / 2; + + if (rotation % 180 != 0) { + matrix.setScale(scaleX, scaleY); + matrix.postTranslate(transX, transY); + } else { + matrix.setScale(scaleY, scaleX); + matrix.postTranslate(transY, transX); + } + return matrix; + } + + @Override + public boolean performClick() { + return super.performClick(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/CameraUtils.java b/src/main/java/com/sharebounds/sharebounds/camera/CameraUtils.java new file mode 100755 index 0000000..26b537b --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/CameraUtils.java @@ -0,0 +1,72 @@ +package com.sharebounds.sharebounds.camera; + +import android.graphics.Matrix; +import android.hardware.Camera; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +class CameraUtils { + + static String editString(String text, ImageGestures.GestureType type) { + text = text.replaceAll("\n", " "); + if (type == ImageGestures.GestureType.LONG_PRESS) text += "\n"; + else text += " "; + return text; + } + + static Matrix fixRotationMatrix(float fixRotation, int imgW, int imgH) { + Matrix matrix = new Matrix(); + matrix.postRotate(fixRotation); + if (fixRotation == 90) matrix.postTranslate(imgW, 0); + else if (fixRotation == -90 || fixRotation == 270) matrix.postTranslate(0, imgH); + else if (fixRotation == 180) matrix.postTranslate(imgW, imgH); + return matrix; + } + + static List intersection(List list1, List list2) { + Set set = new HashSet<>(); + for (T t : list1) { + if (list2.contains(t)) { + set.add(t); + } + } + return new ArrayList<>(set); + } + + static Camera.Size chooseOptimalSize(List outputSizes, int width, int height) { + int MAX = 1920; + int targetW = Math.min(width, MAX); + int targetH = Math.min(height, MAX); + + Camera.Size currentOptimalSize = null; + double currentOptimalDiffW = Double.MAX_VALUE; + double currentOptimalDiffH = Double.MAX_VALUE; + Camera.Size backupSize = null; + double currentBackupDiff = Double.MAX_VALUE; + + for (Camera.Size currentSize : outputSizes) { + if (currentSize.width > MAX || currentSize.height > MAX) continue; + double currentDiffW = currentSize.width - targetW; + double currentDiffH = currentSize.height - targetH; + if (((Math.abs(currentDiffW) < currentOptimalDiffW) && currentDiffH > 0) + || ((Math.abs(currentDiffH) < currentOptimalDiffH) && currentDiffW > 0)) { + currentOptimalSize = currentSize; + currentOptimalDiffW = Math.abs(currentDiffW); + currentOptimalDiffH = Math.abs(currentDiffH); + } else if ((currentOptimalSize == null) && + (Math.abs(currentDiffW) + Math.abs(currentDiffH) < currentBackupDiff)) { + backupSize = currentSize; + currentBackupDiff = Math.abs(currentDiffW) + Math.abs(currentDiffH); + } + } + + if (currentOptimalSize == null) { + if (backupSize != null) currentOptimalSize = backupSize; + else currentOptimalSize = outputSizes.get(0); + } + return currentOptimalSize; + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/CaptureButton.java b/src/main/java/com/sharebounds/sharebounds/camera/CaptureButton.java new file mode 100755 index 0000000..94fa1a1 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/CaptureButton.java @@ -0,0 +1,42 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.util.AttributeSet; + +import com.sharebounds.sharebounds.BaseThemeImageButton; +import com.sharebounds.sharebounds.R; + +public class CaptureButton extends BaseThemeImageButton { + + public CaptureButton(Context context, AttributeSet set) { + super(context, set); + setImage(true); + } + + void nextMode(boolean cameraOn) { + setImage(cameraOn); + setSoundEffectsEnabled(!cameraOn); + } + + @Override + public void setTheme(int theme) { + int tintColor; + int background; + switch (theme) { + case 1: + tintColor = getResources().getColor(R.color.colorDarkPrimary); + background = R.drawable.capture_button_dark; + break; + default: + tintColor = getResources().getColor(R.color.colorPrimary); + background = R.drawable.capture_button; + } + this.setBackgroundResource(background); + this.setColorFilter(tintColor); + this.invalidate(); + } + + private void setImage(boolean cameraOn) { + this.setImageAlpha(cameraOn ? 0 : 255); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/FlashButton.java b/src/main/java/com/sharebounds/sharebounds/camera/FlashButton.java new file mode 100755 index 0000000..45c630b --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/FlashButton.java @@ -0,0 +1,63 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.util.AttributeSet; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.BaseThemeImageButton; +import com.sharebounds.sharebounds.R; + +public class FlashButton extends BaseThemeImageButton { + + enum FlashMode { + AUTO, + ON, + OFF; + + private static FlashMode[] sValues = values(); + private FlashMode next() { + return sValues[(this.ordinal()+1) % sValues.length]; + } + + static String convertToString(int i) { + if (i == 0) return "auto"; + else if(i == 1) return "on"; + else return "off"; + } + } + + private FlashMode mFlashMode; + + public FlashButton(Context context, AttributeSet set) { + super(context, set); + setupButton(); + } + + public void setupButton() { + int lastFlashMode = AppSettings.getInstance(getContext().getApplicationContext()).getFlashMode(); + mFlashMode = FlashMode.values()[lastFlashMode]; + setImage(); + } + + int nextMode() { + mFlashMode = mFlashMode.next(); + AppSettings.getInstance(getContext().getApplicationContext()).setFlashMode(mFlashMode.ordinal()); + setImage(); + return mFlashMode.ordinal(); + } + + private void setImage() { + int newImage; + switch (mFlashMode) { + case ON: + newImage = R.drawable.ic_flash_on_black_24dp; + break; + case OFF: + newImage = R.drawable.ic_flash_off_black_24dp; + break; + default: + newImage = R.drawable.ic_flash_auto_black_24dp; + } + this.setImageResource(newImage); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/ImageData.java b/src/main/java/com/sharebounds/sharebounds/camera/ImageData.java new file mode 100755 index 0000000..c214554 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/ImageData.java @@ -0,0 +1,60 @@ +package com.sharebounds.sharebounds.camera; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +public class ImageData implements Parcelable { + + public Uri uri; + float rotation; + float[] matrixValues; + float fixRotation = 0; + boolean portrait = true; + + public ImageData(Uri uri, float rotation, float[] matrixValues) { + this.uri = uri; + this.rotation = rotation; + this.matrixValues = matrixValues; + } + + private ImageData(Parcel in){ + String uriString = in.readString(); + if (uriString != null) this.uri = Uri.parse(uriString); + this.rotation = in.readFloat(); + int length = in.readInt(); + if (length != -1) { + matrixValues = new float[length]; + in.readFloatArray(matrixValues); + } + portrait = in.readInt() == 1; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + String uriString = null; + if (uri != null) uriString = uri.toString(); + parcel.writeString(uriString); + parcel.writeFloat(rotation); + if (matrixValues != null) { + parcel.writeInt(matrixValues.length); + parcel.writeFloatArray(matrixValues); + } else parcel.writeInt(-1); + parcel.writeInt(portrait ? 1 : 0); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ImageData createFromParcel(Parcel in) { + return new ImageData(in); + } + + public ImageData[] newArray(int size) { + return new ImageData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/ImageGestures.java b/src/main/java/com/sharebounds/sharebounds/camera/ImageGestures.java new file mode 100755 index 0000000..e2761dd --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/ImageGestures.java @@ -0,0 +1,192 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.util.Pair; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; + +class ImageGestures { + + interface GesturesCallBack { + void gestureValues(Matrix matrix); + void imageTouchPoint(float[] touchPoint, GestureType type); + } + + enum GestureType { + SINGLE_TAP, DOUBLE_TAP, LONG_PRESS + } + + private GesturesCallBack mImageGesturesCallBack; + private float mImgW, mImgH, mImgVW, mImgVH; + private Matrix mMatrix = new Matrix(); + private Matrix mFixRot = new Matrix(); + private Matrix mFixRotInv = new Matrix(); + private float[] mValues = new float[9]; + private float mSc, mCurrentSc; + + private ScaleGestureDetector mScaleGestureDetector; + private GestureDetector mGestureDetector; + + ImageGestures(Context context, GesturesCallBack callBack) { + mImageGesturesCallBack = callBack; + mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener()); + mGestureDetector = new GestureDetector(context, new DragGestureListener()); + } + + void onTouch(View view, MotionEvent motionEvent) { + mGestureDetector.onTouchEvent(motionEvent); + mScaleGestureDetector.onTouchEvent(motionEvent); + if (motionEvent.getAction() == MotionEvent.ACTION_UP) view.performClick(); + } + + RectF invertBoundingBox(RectF rect) { + Matrix matrix = new Matrix(mFixRot); + matrix.postConcat(mMatrix); + matrix.mapRect(rect); + return rect; + } + + private float[] convertTouchPoint(MotionEvent event) { + Matrix inverse = new Matrix(); + mMatrix.invert(inverse); + inverse.postConcat(mFixRotInv); + + float[] touchPoint = new float[] {event.getX(), event.getY()}; + inverse.mapPoints(touchPoint); + return touchPoint; + } + + void setOriginValues(Matrix matrix, int imgW, int imgH, Pair sizeValues, + float fixRotation) { + mImgW = imgW; mImgH = imgH; mImgVW = sizeValues.first; mImgVH = sizeValues.second; + mMatrix = matrix; + matrix.getValues(mValues); + mSc = Math.min(mValues[Matrix.MSCALE_X], mValues[Matrix.MSCALE_Y]); + mCurrentSc = mSc; + + mFixRot = CameraUtils.fixRotationMatrix(fixRotation, imgW, imgH); + mFixRot.invert(mFixRotInv); + } + + private void translate(float dX, float dY, boolean scale) { + mMatrix.getValues(mValues); + float trX = mValues[Matrix.MTRANS_X]; + float trY = mValues[Matrix.MTRANS_Y]; + mCurrentSc = Math.min(mValues[Matrix.MSCALE_X], mValues[Matrix.MSCALE_Y]); + + float imgW = mCurrentSc * mImgW; + float imgH = mCurrentSc * mImgH; + + if (imgW < mImgVW) { + if (scale) { + dX = (mImgVW - imgW) / 2 - trX; + } else { + dX = 0; + } + } + else if (trX + dX > 0) dX = -trX; + else if (trX + imgW + dX < mImgVW) dX = mImgVW - (trX + imgW); + + if (imgH < mImgVH) { + if (scale) { + dY = (mImgVH - imgH) / 2 - trY; + } else { + dY = 0; + } + } + else if (trY + dY > 0) dY = -trY; + else if (trY + imgH + dY < mImgVH) dY = mImgVH - (trY + imgH); + + if (dX != 0 || dY != 0 || scale) { + mMatrix.postTranslate(dX, dY); + mImageGesturesCallBack.gestureValues(mMatrix); + } + } + + private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + private float mLastFocusX; + private float mLastFocusY; + private float maxZoom(){ + return 4.0f * mSc; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + mLastFocusX = detector.getFocusX(); + mLastFocusY = detector.getFocusY(); + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + float scale = Math.max(0.1f, Math.min(detector.getScaleFactor(), 5.0f)); + + if (scale < 1.0 && mCurrentSc == mSc) return true; + else if (scale * mCurrentSc - mSc < 0) scale = mSc / mCurrentSc; + else if (mCurrentSc * scale > maxZoom()) scale = maxZoom() / mCurrentSc; + + float focusX = detector.getFocusX(); + float focusY = detector.getFocusY(); + + mMatrix.postScale(scale, scale, focusX, focusY); + translate(focusX - mLastFocusX, focusY - mLastFocusY, true); + + mLastFocusX = focusX; + mLastFocusY = focusY; + return true; + } + } + + private class DragGestureListener extends GestureDetector.SimpleOnGestureListener { + private MotionEvent doubleTap; + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + mImageGesturesCallBack.imageTouchPoint(convertTouchPoint(e), GestureType.SINGLE_TAP); + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + doubleTap = e; + return true; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + if (e.getActionMasked() == MotionEvent.ACTION_UP && doubleTap != null){ + float[] firstTouch = convertTouchPoint(doubleTap); + float[] secondTouch = convertTouchPoint(e); + float[] touchPoints = new float[] {firstTouch[0], firstTouch[1], + secondTouch[0], secondTouch[1]}; + mImageGesturesCallBack.imageTouchPoint(touchPoints, GestureType.DOUBLE_TAP); + doubleTap = null; + } + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + if (doubleTap == null) mImageGesturesCallBack.imageTouchPoint(convertTouchPoint(e), + GestureType.LONG_PRESS); + super.onLongPress(e); + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (!mScaleGestureDetector.isInProgress() && mCurrentSc > mSc) { + translate(-distanceX, -distanceY, false); + } + return true; + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/ImageModel.java b/src/main/java/com/sharebounds/sharebounds/camera/ImageModel.java new file mode 100755 index 0000000..17bef3c --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/ImageModel.java @@ -0,0 +1,74 @@ +package com.sharebounds.sharebounds.camera; + +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.util.SparseArray; +import com.google.android.gms.vision.text.Text; +import com.google.android.gms.vision.text.TextBlock; +import java.util.ArrayList; +import java.util.List; + +class ImageModel { + + Bitmap bitmap; + byte[] bytes; + ImageData imageData; + private SparseArray mTextBlocks; + + ImageModel(Bitmap bitmap, byte[] bytes, ImageData imageData) { + this.bitmap = bitmap; + this.bytes = bytes; + this.imageData = imageData; + } + + void setTextBlocks(SparseArray textBlocks) { + this.mTextBlocks = textBlocks; + } + + List getTextLines() { + float fixRotation = imageData.fixRotation; + Matrix matrix = CameraUtils.fixRotationMatrix(fixRotation, bitmap.getWidth(), bitmap.getHeight()); + + List lines = new ArrayList<>(); + if (mTextBlocks != null) { + for (int i = 0; i < mTextBlocks.size(); i++) { + for (Text currentLine : mTextBlocks.valueAt(i).getComponents()) { + RectF boundingBox = new RectF(currentLine.getBoundingBox()); + if (fixRotation != 0) { + matrix.mapRect(boundingBox); + } + lines.add(boundingBox); + } + } + } + return lines; + } + + OcrData getGestureText(float[] touchPoint, ImageGestures.GestureType type) { + if (mTextBlocks != null) for (int i = 0; i < mTextBlocks.size(); i++) { + TextBlock textBlock = mTextBlocks.valueAt(i); + if (textBlock.getBoundingBox().contains((int) touchPoint[0], (int) touchPoint[1])){ + if (type == ImageGestures.GestureType.LONG_PRESS){ + return new OcrData(textBlock.getValue(), textBlock.getBoundingBox()); + } + for (Text currentLine : textBlock.getComponents()) { + if (currentLine.getBoundingBox().contains((int) touchPoint[0], (int) touchPoint[1])) { + if (type == ImageGestures.GestureType.DOUBLE_TAP && + currentLine.getBoundingBox().contains((int) touchPoint[2], + (int) touchPoint[3])) { + return new OcrData(currentLine.getValue(), currentLine.getBoundingBox()); + } + for (Text currentWord : currentLine.getComponents()) { + if (currentWord.getBoundingBox().contains((int) touchPoint[0], + (int) touchPoint[1])) { + return new OcrData(currentWord.getValue(), currentWord.getBoundingBox()); + } + } + } + } + } + } + return null; + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/OcrCamera.java b/src/main/java/com/sharebounds/sharebounds/camera/OcrCamera.java new file mode 100755 index 0000000..f2326cf --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/OcrCamera.java @@ -0,0 +1,340 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.view.TextureView; +import android.widget.Toast; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.OrientationListener; +import com.sharebounds.sharebounds.PermissionUtils; + +import java.io.IOException; +import java.util.List; + +class OcrCamera implements Camera.ShutterCallback, Camera.PictureCallback { + + interface PictureCallback { + void onPictureTaken(byte[] bytes, int rotation, float[] matrixValues); + } + + private enum FocusMode { Auto, Continuous, None } + private FocusMode mFocusMode = FocusMode.None; + + private final Object mCameraLock = new Object(); + private Handler mMainHandler = new Handler(Looper.getMainLooper()); + private Thread mCameraStartThread; + + private Context mContext; + private Camera mCamera; + private boolean mCameraRunning = false; + private double mAspectRatio = 1.0; + private double mPictureAspectRatio = 1.0; + private int mRotation = 0; + private int mFlashMode; + private float[] mPictureMatrixValues; + private OcrCamera.PictureCallback mPictureCallback; + private CameraTextureView mTextureView; + private CameraAnimationView mCameraAnimationView; + private boolean mWaitingForPicture = false; + + private TextureView.SurfaceTextureListener mSurfaceTextureListener = + new TextureView.SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) { + startCameraPreview(surfaceTexture); + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) { + setScaleValues(); + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + stop(); + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + } + }; + + OcrCamera(Context context, OcrCamera.PictureCallback pictureCallback, CameraTextureView textureView, + CameraAnimationView cameraAnimationView) { + mFlashMode = AppSettings.getInstance(context.getApplicationContext()).getFlashMode(); + mContext = context; + mPictureCallback = pictureCallback; + mTextureView = textureView; + mCameraAnimationView = cameraAnimationView; + } + + boolean start() { + if (mCamera != null || mCameraStartThread != null) { + return true; + } + if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) && + PermissionUtils.getPermission(mContext, PermissionUtils.Type.Camera)) { + synchronized (mCameraLock) { + mCameraStartThread = new Thread(new CameraLoader()); + mCameraStartThread.start(); + return true; + } + } else { + PermissionUtils.errorToast(mContext, PermissionUtils.Type.Camera); + } + return false; + } + + private void startCameraPreview(SurfaceTexture surfaceTexture) { + synchronized (mCameraLock) { + if (mCameraRunning) return; + try { + if (mCamera != null) { + mCamera.setPreviewTexture(surfaceTexture); + mCamera.startPreview(); + resetAutoFocus(); + setScaleValues(); + mCameraRunning = true; + return; + } + } catch (final IOException e) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show(); + } + }); + } + mCameraRunning = false; + } + } + + private void setScaleValues() { + synchronized (mCameraLock) { + mTextureView.scalePreview(mAspectRatio, mRotation); + mPictureMatrixValues = new float[9]; + mTextureView.scaleMatrix(mPictureAspectRatio, mRotation).getValues(mPictureMatrixValues); + } + } + + void stop() { + synchronized (mCameraLock) { + if (mCameraStartThread != null && !mCameraStartThread.isInterrupted()) { + mCameraStartThread.interrupt(); + mCameraStartThread = null; + } + releaseCamera(); + } + } + + private void releaseCamera() { + if (mCamera != null) { + mCamera.stopPreview(); + mCameraRunning = false; + mTextureView.setSurfaceTextureListener(null); + mCamera.release(); + mCamera = null; + } + } + + void resetAutoFocus() { + synchronized (mCameraLock) { + if (mCamera == null || !mCameraRunning) return; + switch (mFocusMode) { + case Continuous: + mCamera.cancelAutoFocus(); + break; + case Auto: + mCamera.autoFocus(null); + break; + } + } + } + + void setFlashMode(int value) { + synchronized (mCameraLock) { + mFlashMode = value; + String flashMode = FlashButton.FlashMode.convertToString(value); + if (mCamera == null) return; + Camera.Parameters params = mCamera.getParameters(); + List flashList = params.getSupportedFlashModes(); + if (flashList == null) return; + if (flashList.contains(flashMode)) { + params.setFlashMode(flashMode); + mCamera.setParameters(params); + } + } + } + + void zoom(float scale) { + synchronized (mCameraLock) { + if (mCamera == null) return; + Camera.Parameters params = mCamera.getParameters(); + if (params.isZoomSupported()) { + int maxZoom = params.getMaxZoom(); + int currentZoom = params.getZoom(); + currentZoom += (scale > 1.0) ? 1 : -1; + if (currentZoom > 0 && currentZoom < maxZoom) { + params.setZoom(currentZoom); + mCamera.cancelAutoFocus(); + mCamera.setParameters(params); + } + } + } + } + + void takePicture() { + synchronized (mCameraLock) { + if (mCamera == null || !mCameraRunning || mWaitingForPicture) return; + mWaitingForPicture = true; + if (mFocusMode == FocusMode.Auto) { + mCamera.autoFocus(mAutoFocusCallback); + } else { + mCamera.takePicture(this, null, this); + } + mCameraAnimationView.shutter(); + } + } + + private Camera.AutoFocusCallback mAutoFocusCallback = new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean b, Camera camera) { + synchronized (mCameraLock) { + if (camera != null && mCameraRunning) + camera.takePicture(null, null, OcrCamera.this); + } + } + }; + + @Override + public void onPictureTaken(byte[] bytes, Camera camera) { + synchronized (mCameraLock) { + mPictureCallback.onPictureTaken(bytes, mRotation, mPictureMatrixValues); + mWaitingForPicture = false; + } + } + + @Override + public void onShutter() {} + + private class CameraLoader implements Runnable { + private boolean mMuteShutter = false; + + @Override + public void run() { + synchronized (mCameraLock) { + if (Thread.currentThread().isInterrupted()) return; + final Exception e = openCamera(); + mMainHandler.post(new Runnable() { + @Override + public void run() { + if (e == null) setupTexture(); + else Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show(); + } + }); + mCameraStartThread = null; + } + } + + private void setupTexture() { + if (mTextureView.isAvailable()) startCameraPreview(mTextureView.getSurfaceTexture()); + mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); + } + + private Exception openCamera() { + try { + int id = findCamera(); + if (id != -1) mCamera = Camera.open(id); + if (mCamera != null) { + setupCamera(); + } + return null; + } catch (Exception e) { + return e; + } + } + + private int findCamera() { + int currentRotation = OrientationListener.getDisplayRotation(mContext); + mRotation = 90; + + int numberOfCameras = Camera.getNumberOfCameras(); + for (int i = 0; i < numberOfCameras; i++) { + Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); + Camera.getCameraInfo(i, cameraInfo); + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { + if (Build.VERSION.SDK_INT >= 17) mMuteShutter = cameraInfo.canDisableShutterSound; + mRotation = (cameraInfo.orientation - currentRotation + 360) % 360; + return i; + } + } + return -1; + } + + private void setupCamera() { + mCamera.setDisplayOrientation(mRotation); + if (Build.VERSION.SDK_INT >= 17 && mMuteShutter) mCamera.enableShutterSound(false); + Camera.Parameters params = mCamera.getParameters(); + setCameraSizes(params); + setFocusMode(params); + mCamera.setParameters(params); + setFlashMode(mFlashMode); + mWaitingForPicture = false; + } + + private void setCameraSizes(Camera.Parameters params) { + int width = mTextureView.getWidth(); + if (width == 0) width = Resources.getSystem().getDisplayMetrics().widthPixels; + int height = mTextureView.getLayoutParams().height; + if (height == 0) height = mTextureView.getHeight(); + if (height == 0) height = Resources.getSystem().getDisplayMetrics().heightPixels; + + if (mRotation == 0 || mRotation == 180) { + int tmp = width; + width = height; + height = tmp; + } + List previewSizes = params.getSupportedPreviewSizes(); + List pictureSizes = params.getSupportedPictureSizes(); + List intersection = CameraUtils.intersection(previewSizes, pictureSizes); + + Camera.Size previewSize; + Camera.Size pictureSize; + if (intersection.size() > 2) { + previewSize = CameraUtils.chooseOptimalSize(intersection, width, height); + pictureSize = previewSize; + } else { + previewSize = CameraUtils.chooseOptimalSize(previewSizes, width, height); + + if (pictureSizes.contains(previewSize)) { + pictureSize = previewSize; + } else { + pictureSize = CameraUtils.chooseOptimalSize(pictureSizes, previewSize.width, previewSize.height); + } + } + params.setPreviewSize(previewSize.width, previewSize.height); + params.setPictureSize(pictureSize.width, pictureSize.height); + mAspectRatio = (double) previewSize.height / (double) previewSize.width; + mPictureAspectRatio = (double) pictureSize.height / (double) pictureSize.width; + } + + private void setFocusMode(Camera.Parameters params) { + List focusModes = params.getSupportedFocusModes(); + if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { + params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); + mFocusMode = FocusMode.Continuous; + } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { + params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + mFocusMode = FocusMode.Auto; + } + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/OcrCameraController.java b/src/main/java/com/sharebounds/sharebounds/camera/OcrCameraController.java new file mode 100755 index 0000000..240fa01 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/OcrCameraController.java @@ -0,0 +1,218 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.support.constraint.ConstraintLayout; +import android.util.Pair; +import android.view.MotionEvent; +import android.view.View; + +import com.sharebounds.sharebounds.R; + +public class OcrCameraController implements View.OnClickListener, View.OnTouchListener, + ImageGestures.GesturesCallBack, CameraGestures.GesturesCallBack, OcrCamera.PictureCallback { + + public interface OcrCameraListener { + void onNewOcrText(String text); + void onCameraEvent(boolean on); + } + + private boolean mRotateImage = true; + private int currentRotation = 0; + private OcrCameraListener mListener; + private OcrCamera mOcrCamera; + private OcrTask mOcrTask; + private ImageGestures mImageGestures; + private CameraGestures mCameraGestures; + private BitmapImageView mBitmapImageView; + private CameraTextureView mCameraTextureView; + private CaptureButton mCaptureButton; + + private boolean cameraIsRunning = true; + private void setCameraIsRunning(boolean bool) { + if (bool != cameraIsRunning) { + mCaptureButton.nextMode(bool); + mListener.onCameraEvent(bool); + cameraIsRunning = bool; + } + } + + private ImageModel currentImageModel; + public ImageData getParcelableData() { + if (currentImageModel == null) return null; + return currentImageModel.imageData; + } + public byte[] getBytes() { + if (currentImageModel == null) return null; + return currentImageModel.bytes; + } + public Pair getImageViewSize() { + int width = (mBitmapImageView.getLayoutParams().width == -1 + || mBitmapImageView.getLayoutParams().width == 0) + ? mBitmapImageView.getWidth() : mBitmapImageView.getLayoutParams().width; + int height = (mBitmapImageView.getLayoutParams().height == -1 + || mBitmapImageView.getLayoutParams().height == 0) + ? mBitmapImageView.getHeight() : mBitmapImageView.getLayoutParams().height; + return new Pair<>(width, height); + } + + public OcrCameraController(Context context, ConstraintLayout constraintLayout) { + this(context, constraintLayout, true); + } + + public OcrCameraController(Context context, ConstraintLayout constraintLayout, boolean rotateImage) { + if (context instanceof OcrCameraListener) { + mListener = (OcrCameraListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OcrCameraTextListener"); + } + mRotateImage = rotateImage; + mBitmapImageView = constraintLayout.findViewById(R.id.camera_image_view); + mCaptureButton = constraintLayout.findViewById(R.id.capture_button); + mCaptureButton.setOnClickListener(this); + constraintLayout.findViewById(R.id.flash_button).setOnClickListener(this); + + mImageGestures = new ImageGestures(context, this); + mBitmapImageView.setOnTouchListener(this); + + mCameraGestures = new CameraGestures(context, this); + mCameraTextureView = constraintLayout.findViewById(R.id.camera_texture); + mCameraTextureView.setOnTouchListener(this); + + CameraAnimationView cameraAnimationView = constraintLayout.findViewById(R.id.camera_animation); + mOcrCamera = new OcrCamera(context, this, mCameraTextureView, cameraAnimationView); + mOcrTask = new OcrTask(context, this); + } + + public void setup() { + mOcrTask.setup(); + } + + public void release() { + mOcrTask.release(); + } + + public void destroy() { + mListener = null; + } + + public void setTheme(int theme) { + mOcrTask.setTheme(theme); + mBitmapImageView.setTheme(theme); + } + + public void onRotation(final int newRotation) { + currentRotation = newRotation; + if (mRotateImage && !cameraIsRunning) { + mBitmapImageView.endAnimation(); + if (mCameraTextureView.getAlpha() == 1.0f) mCameraTextureView.reset(); + if (currentImageModel != null) setImageModel(currentImageModel); + } + } + + public void resumeCamera(boolean start) { + if (cameraIsRunning || start) { + boolean cameraStarted = mOcrCamera.start(); + setCameraIsRunning(cameraStarted); + } + } + + public void pauseCamera(boolean stop) { + if (cameraIsRunning || stop) { + mOcrCamera.stop(); + if (stop) setCameraIsRunning(false); + } + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + if (view == mCameraTextureView && cameraIsRunning) + mCameraGestures.onTouch(view, motionEvent); + else if (view == mBitmapImageView && !cameraIsRunning) + mImageGestures.onTouch(view, motionEvent); + else if (motionEvent.getAction() == MotionEvent.ACTION_UP) view.performClick(); + return true; + } + + public void onClick(View view) { + switch (view.getId()) { + case R.id.flash_button: + mOcrCamera.setFlashMode(((FlashButton) view).nextMode()); + break; + case R.id.capture_button: + mOcrTask.cancelTask(); + currentImageModel = null; + if (cameraIsRunning) { + mOcrCamera.takePicture(); + } else { + mBitmapImageView.reset(); + mCameraTextureView.visible(true); + resumeCamera(true); + } + break; + } + } + + public void onPictureTaken(byte[] bytes, int rotation, float[] matrixValues) { + pauseCamera(true); + final float rot = -currentRotation + rotation; + ImageData imageData = new ImageData(null, rot, matrixValues); + if (!mRotateImage) { + imageData.fixRotation = currentRotation; + imageData.portrait = false; + } + processImage(null, bytes, imageData); + } + + public void processImage(final Bitmap bitmap, final byte[] bytes, final ImageData imageData) { + mOcrTask.cancelTask(); + + if (bitmap != null || bytes != null) { + ImageModel model = new ImageModel(bitmap, bytes, imageData); + mOcrTask.startTask(model); + } else { + resumeCamera(true); + } + } + + void setImageModel(ImageModel model) { + Bitmap bitmap = model.bitmap; + float fixRot = model.imageData.fixRotation; + if (!cameraIsRunning && bitmap != null) { + currentImageModel = model; + Pair sizeValues = getImageViewSize(); + Matrix rotM = mBitmapImageView.setImageViewBitmap(bitmap, sizeValues); + mImageGestures.setOriginValues(rotM, bitmap.getWidth(), bitmap.getHeight(), sizeValues, fixRot); + mCameraTextureView.visible(false); + } + } + + @Override + public void gestureValues(Matrix matrix) { + mBitmapImageView.endAnimation(); + mBitmapImageView.setImageMatrix(matrix); + mBitmapImageView.invalidate(); + } + + @Override + public void imageTouchPoint(float[] touchPoint, ImageGestures.GestureType type) { + if (mBitmapImageView.animatorSet.isRunning()) return; + OcrData textData = currentImageModel.getGestureText(touchPoint, type); + if (textData != null && mListener != null) { + mBitmapImageView.animate(mImageGestures.invertBoundingBox(textData.boundingBox)); + mListener.onNewOcrText(CameraUtils.editString(textData.text, type)); + } + } + + @Override + public void cameraZoom(float scale) { + mOcrCamera.zoom(scale); + } + + @Override + public void cameraTap() { + mOcrCamera.resetAutoFocus(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/OcrData.java b/src/main/java/com/sharebounds/sharebounds/camera/OcrData.java new file mode 100755 index 0000000..7e46c49 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/OcrData.java @@ -0,0 +1,15 @@ +package com.sharebounds.sharebounds.camera; + +import android.graphics.Rect; +import android.graphics.RectF; + +class OcrData { + + final String text; + final RectF boundingBox; + + OcrData(String text, Rect boundingBox) { + this.text = text; + this.boundingBox = new RectF(boundingBox); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/OcrDetector.java b/src/main/java/com/sharebounds/sharebounds/camera/OcrDetector.java new file mode 100755 index 0000000..b3232cd --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/OcrDetector.java @@ -0,0 +1,72 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.SparseArray; +import android.widget.Toast; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; +import com.google.android.gms.vision.Frame; +import com.google.android.gms.vision.text.TextBlock; +import com.google.android.gms.vision.text.TextRecognizer; +import com.sharebounds.sharebounds.R; + +class OcrDetector { + + enum Status { Available, Released, Failed } + + private Status mDetectorStatus = Status.Failed; + + Status getStatus() { + return mDetectorStatus; + } + + private Context mContext; + private TextRecognizer mTextRecognizer; + + static private int convertRotation(float rotation) { + return Math.round(((rotation + 360) % 360) / 90); + } + + OcrDetector(Context context) { + mContext = context; + setup(); + } + + void setup() { + if (mTextRecognizer != null) return; + + Context context = mContext.getApplicationContext(); + int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context); + if (code == ConnectionResult.SUCCESS || code == ConnectionResult.SERVICE_UPDATING || + code == ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED) + mTextRecognizer = new TextRecognizer.Builder(context).build(); + if (mTextRecognizer == null || !mTextRecognizer.isOperational()) { + Toast.makeText(mContext, + R.string.ocr_loading_error, + Toast.LENGTH_LONG).show(); + release(); + mDetectorStatus = Status.Failed; + } else { + mDetectorStatus = Status.Available; + } + } + + void release(){ + if (mTextRecognizer != null) { + mDetectorStatus = Status.Released; + mTextRecognizer.release(); + mTextRecognizer = null; + } + } + + SparseArray detect(Bitmap imageBitmap, float rotation) { + if (mTextRecognizer != null) { + Frame imageFrame = new Frame.Builder().setBitmap(imageBitmap) + .setRotation(OcrDetector.convertRotation(rotation)).build(); + return mTextRecognizer.detect(imageFrame); + } + return null; + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/camera/OcrTask.java b/src/main/java/com/sharebounds/sharebounds/camera/OcrTask.java new file mode 100755 index 0000000..a01cb08 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/camera/OcrTask.java @@ -0,0 +1,110 @@ +package com.sharebounds.sharebounds.camera; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.RectF; +import android.os.AsyncTask; + +import java.lang.ref.WeakReference; +import java.util.List; + +class OcrTask { + + private final Object mDetectorLock = new Object(); + private OcrDetector mOcrDetector; + private BitmapUtils mBitmapUtils; + private OcrCameraController mOcrCameraController; + private OcrDetectorTask mOcrDetectorTask; + + OcrTask(Context context, OcrCameraController cameraController) { + mOcrDetector = new OcrDetector(context); + mBitmapUtils = new BitmapUtils(context); + mOcrCameraController = cameraController; + } + + void setup() { + mOcrDetector.setup(); + } + + void release() { + synchronized (mDetectorLock) { + cancelTask(); + mOcrDetector.release(); + } + } + + void setTheme(int theme) { + mBitmapUtils.setTheme(theme); + } + + void startTask(ImageModel imageModel) { + mOcrDetectorTask = new OcrDetectorTask(this, imageModel); + mOcrDetectorTask.execute(); + } + + private boolean isRunning() { + return (mOcrDetectorTask != null && mOcrDetectorTask.getStatus() != AsyncTask.Status.FINISHED); + } + + void cancelTask() { + if (isRunning()) { + mOcrDetectorTask.cancel(true); + mOcrDetectorTask = null; + } + } + + private void setControllerModel(final ImageModel model) { + mOcrCameraController.setImageModel(model); + mOcrDetectorTask = null; + } + + private static class OcrDetectorTask extends AsyncTask { + + private final WeakReference mOcrTaskRef; + private final ImageModel mImageModel; + + OcrDetectorTask(OcrTask ocrTask, ImageModel imageModel) { + this.mOcrTaskRef = new WeakReference<>(ocrTask); + this.mImageModel = imageModel; + } + + @Override + protected ImageModel doInBackground(Float... floats) { + OcrTask ocrTask = mOcrTaskRef.get(); + if (ocrTask != null) { + byte[] bytes = mImageModel.bytes; + Bitmap bitmap = mImageModel.bitmap; + float fixRotation = mImageModel.imageData.fixRotation; + + if (bytes != null) { + bitmap = BitmapUtils.convertToBitmap(bytes, mImageModel.imageData); + } else if (!bitmap.isMutable()) { + bitmap = BitmapUtils.mutableCopy(bitmap); + } + mImageModel.bitmap = bitmap; + + synchronized (ocrTask.mDetectorLock) { + OcrDetector.Status detectorStatus = ocrTask.mOcrDetector.getStatus(); + if (isCancelled() || detectorStatus == OcrDetector.Status.Released) return null; + else if (detectorStatus == OcrDetector.Status.Available) + mImageModel.setTextBlocks(ocrTask.mOcrDetector.detect(bitmap, -fixRotation)); + } + List lines = mImageModel.getTextLines(); + if (lines != null && lines.size() != 0) { + ocrTask.mBitmapUtils.drawImageRect(bitmap, lines); + } + return mImageModel; + } + return null; + } + + @Override + protected void onPostExecute(ImageModel imageModel) { + super.onPostExecute(imageModel); + OcrTask ocrTask = mOcrTaskRef.get(); + if (ocrTask != null && imageModel != null && !isCancelled()) { + ocrTask.setControllerModel(imageModel); + } + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/keyboard/CameraIME.java b/src/main/java/com/sharebounds/sharebounds/keyboard/CameraIME.java new file mode 100755 index 0000000..074071f --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/keyboard/CameraIME.java @@ -0,0 +1,247 @@ +package com.sharebounds.sharebounds.keyboard; + +import android.app.Dialog; +import android.content.Context; +import android.hardware.SensorManager; +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard; +import android.os.Build; +import android.os.IBinder; +import android.support.constraint.ConstraintLayout; +import android.view.View; +import android.view.Window; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.Toast; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.OrientationListener; +import com.sharebounds.sharebounds.R; +import com.sharebounds.sharebounds.camera.OcrCameraController; + +public class CameraIME extends InputMethodService implements OcrCameraController.OcrCameraListener, + OrientationListener.OrientationCallBack { + + private CameraIMEUIController mCameraIMEUIController; + private OcrCameraController mOcrCameraController; + private OrientationListener mOrientationListener; + + private boolean mDidFinish = true; + + private int mGlobeFunction; + private boolean mFullScreenMode; + private InputMethodManager mInputMethodManager; + private KeyboardTextManager mKeyboardTextManager; + + @Override + public void onCreate() { + super.onCreate(); + mInputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + mKeyboardTextManager = new KeyboardTextManager(this); + } + + @Override + public View onCreateInputView() { + if (mOcrCameraController != null && !mDidFinish) { + mOcrCameraController.pauseCamera(true); + mOcrCameraController.release(); + } + if (mOcrCameraController != null) mOcrCameraController.destroy(); + + ConstraintLayout keyboardLayout = + (ConstraintLayout) getLayoutInflater().inflate(R.layout.keyboard, null); + mCameraIMEUIController = new CameraIMEUIController(keyboardLayout); + mOcrCameraController = new OcrCameraController(this, keyboardLayout, false); + mFullScreenMode = AppSettings.getInstance(getApplicationContext()).getIsFullScreen(); + updateKeyboardHeight(); + return keyboardLayout; + } + + @Override + public void onStartInputView(EditorInfo info, boolean restarting) { + super.onStartInputView(info, restarting); + if (mDidFinish) { + mCameraIMEUIController.resetFlashButton(); + mKeyboardTextManager.playSound = AppSettings.getInstance(getApplicationContext()).getKeyboardSound(); + mKeyboardTextManager.vibration = AppSettings.getInstance(getApplicationContext()).getKeyboardVibration(); + mGlobeFunction = AppSettings.getInstance(getApplicationContext()).getGlobeFunction(); + + int newTheme = AppSettings.getInstance(getApplicationContext()).getTheme(); + if (mCameraIMEUIController.currentTheme != newTheme) { + mCameraIMEUIController.setTheme(newTheme); + mOcrCameraController.setTheme(newTheme); + } + + if (!AppSettings.getInstance(getApplicationContext()).getTermsAccepted()) { + if (!restarting) Toast.makeText(this, getString(R.string.acc_terms), + Toast.LENGTH_LONG).show(); + return; + } + + mOrientationListener = new OrientationListener(this, SensorManager.SENSOR_DELAY_NORMAL, + this); + if (mOrientationListener.canDetectOrientation()) { + mOrientationListener.enable(); + } + + mOcrCameraController.setup(); + mDidFinish = false; + } else { + mOrientationListener.setup(this); + } + mOcrCameraController.resumeCamera(false); + } + + @Override + public void onFinishInputView(boolean finishingInput) { + super.onFinishInputView(finishingInput); + mDidFinish = true; + release(); + } + + private void release() { + if (mOrientationListener != null) { + mOrientationListener.disable(); + mOrientationListener.release(); + mOrientationListener = null; + } + mOcrCameraController.pauseCamera(false); + mOcrCameraController.release(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (!mDidFinish) release(); + } + + @Override + public void onRotation(int oldRotation, int newRotation) { + if (newRotation == 180 && Utils.isOrientationUnlocked(this)) { + mCameraIMEUIController.bitmapImageView.post(new Runnable() { + @Override + public void run() { + mOcrCameraController.pauseCamera(false); + mOcrCameraController.resumeCamera(false); + mOrientationListener.setup(CameraIME.this); + } + }); + } + mOcrCameraController.onRotation(newRotation); + } + + private void updateKeyboardHeight() { + int recommendedHeight = 0; + if (Build.VERSION.SDK_INT >= 21) recommendedHeight = getInputMethodWindowRecommendedHeight(); + mCameraIMEUIController.setKeyboardHeight(mFullScreenMode, recommendedHeight); + updateFullscreenMode(); + } + + @Override + public void onCameraEvent(boolean on) { + mCameraIMEUIController.enableEditUI(!on); + } + + public void onNewOcrText(String text) { + mKeyboardTextManager.insertText(text, getCurrentInputConnection(), + mCameraIMEUIController.bitmapImageView); + } + + public void kbFullScreenButtonClick(View view) { + mFullScreenMode = ((FullScreenButton) view).nextMode(); + updateKeyboardHeight(); + } + + private IBinder getToken() { + final Dialog dialog = getWindow(); + if (dialog == null) { + return null; + } + final Window window = dialog.getWindow(); + if (window == null) { + return null; + } + return window.getAttributes().token; + } + + public void kbGlobeButtonClick(View view) { + if (!view.isPressed()) { + mKeyboardTextManager.soundClick(0, view); + } else { + switchKeyboard(); + } + } + + private void switchKeyboard() { + final IBinder token = getToken(); + if (token == null) { + mInputMethodManager.showInputMethodPicker(); + return; + } + + switch(mGlobeFunction) { + case 2: + mInputMethodManager.showInputMethodPicker(); + break; + case 1: + if (mInputMethodManager.switchToLastInputMethod(token)) { + break; + } + default: + if (!mInputMethodManager.switchToNextInputMethod(token, false)) { + if (mGlobeFunction != 1 && mInputMethodManager.switchToLastInputMethod(token)) { + break; + } + mInputMethodManager.showInputMethodPicker(); + } + } + } + + public void kbButtonClick(View view) { + int keyCode; + switch (view.getId()) { + case (R.id.kb_space_button): + keyCode = 32; + break; + default: + keyCode = Keyboard.KEYCODE_DONE; + } + kbButtonAction(view, keyCode); + } + + private void kbButtonAction(View view, int keyCode) { + if (!view.isPressed()) { + mKeyboardTextManager.soundClick(keyCode, view); + } else { + mKeyboardTextManager.onKey(keyCode, getCurrentInputConnection()); + } + } + + public void kbBackspaceButtonClick(View view) { + KeyboardRepeatButton keyboardRepeatButton = (KeyboardRepeatButton) view; + if (keyboardRepeatButton.onClickDown) { + mKeyboardTextManager.soundClick(Keyboard.KEYCODE_DELETE, view); + keyboardRepeatButton.onClickDown = false; + return; + } + + InputConnection inputConnection = getCurrentInputConnection(); + if (inputConnection != null && inputConnection.getSelectedText(0) == null) { + CharSequence charSequence = inputConnection.getTextBeforeCursor(1, 0); + if (charSequence != null && charSequence.length() == 0) return; + } + + if (keyboardRepeatButton.repeating){ + mKeyboardTextManager.soundClick(Keyboard.KEYCODE_DELETE, view); + mKeyboardTextManager.onKey(Keyboard.KEYCODE_DELETE, getCurrentInputConnection()); + } else { + mKeyboardTextManager.onKey(Keyboard.KEYCODE_DELETE, getCurrentInputConnection()); + } + } + + @Override + public boolean onEvaluateFullscreenMode() { + return mFullScreenMode; + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/keyboard/CameraIMEUIController.java b/src/main/java/com/sharebounds/sharebounds/keyboard/CameraIMEUIController.java new file mode 100755 index 0000000..1ca3e38 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/keyboard/CameraIMEUIController.java @@ -0,0 +1,104 @@ +package com.sharebounds.sharebounds.keyboard; + +import android.content.res.Resources; +import android.support.constraint.ConstraintLayout; +import android.view.View; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.BaseThemeImageButton; +import com.sharebounds.sharebounds.R; +import com.sharebounds.sharebounds.camera.BitmapImageView; +import com.sharebounds.sharebounds.camera.CameraTextureView; +import com.sharebounds.sharebounds.camera.FlashButton; + +class CameraIMEUIController { + + int currentTheme; + private ConstraintLayout mKeyboardView; + BitmapImageView bitmapImageView; + private CameraTextureView mCameraTextureView; + + private FlashButton mFlashButton; + private FullScreenButton mFullScreenButton; + + CameraIMEUIController(ConstraintLayout constraintLayout){ + mKeyboardView = constraintLayout; + currentTheme = AppSettings.getInstance(mKeyboardView.getContext().getApplicationContext()).getTheme(); + setupViews(); + } + + private void setupViews() { + bitmapImageView = mKeyboardView.findViewById(R.id.camera_image_view); + mCameraTextureView = mKeyboardView.findViewById(R.id.camera_texture); + mFlashButton = mKeyboardView.findViewById(R.id.flash_button); + mFullScreenButton = mKeyboardView.findViewById(R.id.kb_full_screen_button); + mKeyboardView.findViewById(R.id.kb_space_button).setOnTouchListener(new KeyClickListener()); + mKeyboardView.findViewById(R.id.kb_return_button).setOnTouchListener(new KeyClickListener()); + mKeyboardView.findViewById(R.id.kb_globe_button).setOnTouchListener(new KeyClickListener()); + } + + void setTheme(int theme) { + currentTheme = theme; + for (int i = 0; i < mKeyboardView.getChildCount(); i++) { + View view = mKeyboardView.getChildAt(i); + if (view instanceof BaseThemeImageButton) { + ((BaseThemeImageButton) view).setTheme(theme); + } + } + } + + void setKeyboardHeight(boolean fullScreenMode, int recommendedHeight) { + int height = Resources.getSystem().getDisplayMetrics().heightPixels; + height *= (fullScreenMode) ? (0.75) : + (standardKeyboardHeight(recommendedHeight / height, + Utils.isLandscapeMode())); + bitmapImageView.getLayoutParams().height = height; + mCameraTextureView.getLayoutParams().height = height; + bitmapImageView.forceLayout(); + mCameraTextureView.forceLayout(); + mKeyboardView.requestLayout(); + } + + private double standardKeyboardHeight(double recommendedHeight, boolean landscape) { + if ((recommendedHeight > 0.25) && (recommendedHeight < 0.4)) { + return recommendedHeight; + } + return landscape ? 0.5 : 0.35; + } + + void resetFlashButton() { + mFlashButton.setupButton(); + } + + void enableEditUI(boolean enabled) { + mFullScreenButton.setEnabled(!enabled); + mFlashButton.setEnabled(!enabled); + if (enabled) { + mFullScreenButton.animate().alpha(0.0f).setDuration(300).withEndAction(new Runnable() { + @Override + public void run() { + mFullScreenButton.setVisibility(View.INVISIBLE); + } + }); + mFlashButton.animate().alpha(0.0f).setDuration(300).withEndAction(new Runnable() { + @Override + public void run() { + mFlashButton.setVisibility(View.INVISIBLE); + } + }); + } else { + mFullScreenButton.animate().alpha(1.0f).setDuration(300).withStartAction(new Runnable() { + @Override + public void run() { + mFullScreenButton.setVisibility(View.VISIBLE); + } + }); + mFlashButton.animate().alpha(1.0f).setDuration(300).withStartAction(new Runnable() { + @Override + public void run() { + mFlashButton.setVisibility(View.VISIBLE); + } + }); + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/keyboard/FullScreenButton.java b/src/main/java/com/sharebounds/sharebounds/keyboard/FullScreenButton.java new file mode 100755 index 0000000..44c0cf6 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/keyboard/FullScreenButton.java @@ -0,0 +1,31 @@ +package com.sharebounds.sharebounds.keyboard; + +import android.content.Context; +import android.util.AttributeSet; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.BaseThemeImageButton; +import com.sharebounds.sharebounds.R; + +class FullScreenButton extends BaseThemeImageButton { + + private boolean mIsFullScreen; + + public FullScreenButton(Context context, AttributeSet set) { + super(context, set); + mIsFullScreen = AppSettings.getInstance(context.getApplicationContext()).getIsFullScreen(); + setImage(); + } + + boolean nextMode() { + mIsFullScreen = !mIsFullScreen; + AppSettings.getInstance(getContext().getApplicationContext()).setIsFullScreen(mIsFullScreen); + setImage(); + return mIsFullScreen; + } + + private void setImage() { + this.setImageResource(mIsFullScreen ? + R.drawable.ic_fullscreen_exit_black_24dp : R.drawable.ic_fullscreen_black_24dp); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/keyboard/KeyClickListener.java b/src/main/java/com/sharebounds/sharebounds/keyboard/KeyClickListener.java new file mode 100755 index 0000000..da30643 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/keyboard/KeyClickListener.java @@ -0,0 +1,43 @@ +package com.sharebounds.sharebounds.keyboard; + +import android.graphics.Rect; +import android.view.MotionEvent; +import android.view.View; + +class KeyClickListener implements View.OnTouchListener { + + private Rect mRect; + + void setRect(View view) { + mRect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); + } + + boolean rectContains(View view, MotionEvent motionEvent) { + return mRect.contains(view.getLeft() + (int) motionEvent.getX(), + view.getTop() + (int) motionEvent.getY()); + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + switch(motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + setRect(view); + view.performClick(); + view.setPressed(true); + return true; + case MotionEvent.ACTION_MOVE: + if(!rectContains(view, motionEvent)){ + view.setPressed(false); + } + break; + case MotionEvent.ACTION_UP: + if (rectContains(view, motionEvent)) { + view.performClick(); + } + case MotionEvent.ACTION_CANCEL: + view.setPressed(false); + return true; + } + return false; + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/keyboard/KeyboardRepeatButton.java b/src/main/java/com/sharebounds/sharebounds/keyboard/KeyboardRepeatButton.java new file mode 100755 index 0000000..65937c3 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/keyboard/KeyboardRepeatButton.java @@ -0,0 +1,22 @@ +package com.sharebounds.sharebounds.keyboard; + +import android.content.Context; +import android.util.AttributeSet; + +import com.sharebounds.sharebounds.BaseThemeImageButton; + +class KeyboardRepeatButton extends BaseThemeImageButton { + + boolean onClickDown; + boolean repeating; + + public KeyboardRepeatButton(Context context, AttributeSet set) { + super(context, set); + this.setOnTouchListener(new RepeatListener()); + } + + @Override + public boolean performClick() { + return super.performClick(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/keyboard/KeyboardTextManager.java b/src/main/java/com/sharebounds/sharebounds/keyboard/KeyboardTextManager.java new file mode 100755 index 0000000..8e62295 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/keyboard/KeyboardTextManager.java @@ -0,0 +1,73 @@ +package com.sharebounds.sharebounds.keyboard; + +import android.content.Context; +import android.inputmethodservice.Keyboard; +import android.media.AudioManager; +import android.view.HapticFeedbackConstants; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.InputConnection; + +import static android.content.Context.AUDIO_SERVICE; + +class KeyboardTextManager { + + private AudioManager mAudioManager; + + boolean playSound; + boolean vibration; + + KeyboardTextManager(Context context) { + mAudioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE); + } + + void onKey(int i, InputConnection inputConnection) { + if (inputConnection == null) return; + switch(i){ + case Keyboard.KEYCODE_DELETE: + keyDownUp(KeyEvent.KEYCODE_DEL, inputConnection); + break; + case Keyboard.KEYCODE_DONE: + keyDownUp(KeyEvent.KEYCODE_ENTER, inputConnection); + break; + default: + char code = (char)i; + inputConnection.commitText(String.valueOf(code),1); + } + } + + void insertText(String text, InputConnection inputConnection, View view) { + soundClick(0, view); + if (inputConnection == null) return; + inputConnection.commitText(text, 1); + } + + private void keyDownUp(int keyEventCode, InputConnection inputConnection) { + inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); + inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); + } + + void soundClick(int i, View view) { + if (playSound) { + playClick(i); + } + if (vibration) { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + } + } + + private void playClick(int keyCode){ + int sound = AudioManager.FX_KEYPRESS_STANDARD; + switch(keyCode){ + case 32: + sound = AudioManager.FX_KEYPRESS_SPACEBAR; + break; + case Keyboard.KEYCODE_DONE: + sound = AudioManager.FX_KEYPRESS_RETURN; + break; + case Keyboard.KEYCODE_DELETE: + sound = AudioManager.FX_KEYPRESS_DELETE; + } + mAudioManager.playSoundEffect(sound); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/keyboard/RepeatListener.java b/src/main/java/com/sharebounds/sharebounds/keyboard/RepeatListener.java new file mode 100755 index 0000000..49953da --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/keyboard/RepeatListener.java @@ -0,0 +1,54 @@ +package com.sharebounds.sharebounds.keyboard; + +import android.os.Handler; +import android.view.MotionEvent; +import android.view.View; + +class RepeatListener extends KeyClickListener { + + private static final int INITIAL_TIME = 500; + private static final int INTERVAL_TIME = 100; + + private Handler mHandler = new Handler(); + private KeyboardRepeatButton mButtonView; + + private Runnable mAction = new Runnable() { + @Override public void run() { + mHandler.postDelayed(this, INTERVAL_TIME); + mButtonView.repeating = true; + mButtonView.performClick(); + } + }; + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + switch(motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: + mButtonView = (KeyboardRepeatButton) view; + mButtonView.onClickDown = true; + mButtonView.repeating = false; + setRect(view); + mButtonView.performClick(); + mButtonView.setPressed(true); + mHandler.removeCallbacks(mAction); + mHandler.postDelayed(mAction, INITIAL_TIME); + return true; + case MotionEvent.ACTION_MOVE: + if(!rectContains(view, motionEvent) && !mButtonView.repeating){ + mHandler.removeCallbacks(mAction); + mButtonView.setPressed(false); + } + break; + case MotionEvent.ACTION_UP: + if (!mButtonView.repeating && rectContains(view, motionEvent)) { + mButtonView.performClick(); + } + case MotionEvent.ACTION_CANCEL: + mHandler.removeCallbacks(mAction); + mButtonView.setPressed(false); + mButtonView = null; + return true; + } + return true; + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/keyboard/Utils.java b/src/main/java/com/sharebounds/sharebounds/keyboard/Utils.java new file mode 100755 index 0000000..2fb1738 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/keyboard/Utils.java @@ -0,0 +1,18 @@ +package com.sharebounds.sharebounds.keyboard; + +import android.content.Context; +import android.content.res.Resources; +import android.provider.Settings; + +class Utils { + + static boolean isLandscapeMode() { + return (Resources.getSystem().getDisplayMetrics().heightPixels < + Resources.getSystem().getDisplayMetrics().widthPixels); + } + + static boolean isOrientationUnlocked(Context context) { + return (android.provider.Settings.System.getInt(context.getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION, 0) == 1); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/BaseThemeActivity.java b/src/main/java/com/sharebounds/sharebounds/main/BaseThemeActivity.java new file mode 100755 index 0000000..f30c847 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/BaseThemeActivity.java @@ -0,0 +1,40 @@ +package com.sharebounds.sharebounds.main; + +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.R; + +public class BaseThemeActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + int theme = AppSettings.getInstance(getApplicationContext()).getTheme(); + setTheme(theme); + + setupActionBar(); + } + + @Override + public void setTheme(int theme) { + int newTheme; + switch (theme) { + case 1: + newTheme = R.style.AppThemeDark; + break; + default: + newTheme = R.style.AppTheme; + } + super.setTheme(newTheme); + } + + private void setupActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/BottomSheetFragment.java b/src/main/java/com/sharebounds/sharebounds/main/BottomSheetFragment.java new file mode 100755 index 0000000..17b870d --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/BottomSheetFragment.java @@ -0,0 +1,23 @@ +package com.sharebounds.sharebounds.main; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.BottomSheetDialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.sharebounds.sharebounds.R; + +public class BottomSheetFragment extends BottomSheetDialogFragment { + + public BottomSheetFragment() {} + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_bottom_sheet, container, false); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/GalleryImageLoader.java b/src/main/java/com/sharebounds/sharebounds/main/GalleryImageLoader.java new file mode 100755 index 0000000..8d65ac0 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/GalleryImageLoader.java @@ -0,0 +1,130 @@ +package com.sharebounds.sharebounds.main; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Pair; +import android.widget.Toast; + +import com.sharebounds.sharebounds.R; +import com.squareup.picasso.MemoryPolicy; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.lang.ref.WeakReference; + +class GalleryImageLoader { + + private MainActivity mMainActivity; + private static String FILE_NAME = "sb-RestoredStateImage-00"; + + RestoreBytesTask restoreBytesTask; + + GalleryImageLoader(MainActivity activity) { + mMainActivity = activity; + } + + void cancelRequest() { + Picasso.get().cancelRequest(mImageTarget); + cancelRestoreTask(); + } + + private void cancelRestoreTask() { + if (restoreBytesTask != null && restoreBytesTask.getStatus() != AsyncTask.Status.FINISHED) { + restoreBytesTask.cancel(false); + restoreBytesTask = null; + } + } + + private void errorToast(String error) { + Toast.makeText(mMainActivity, error, Toast.LENGTH_LONG).show(); + } + + void loadSetImage(Uri uri, Pair imageViewSize) { + cancelRequest(); + int maxSize = (imageViewSize.first == 0) ? 1920 : + (Math.max(imageViewSize.first, imageViewSize.second)); + Picasso.get().load(uri).memoryPolicy(MemoryPolicy.NO_CACHE) + .resize(maxSize, maxSize).centerInside().onlyScaleDown().into(mImageTarget); + } + + private Target mImageTarget = new Target() { + @Override + public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { + mMainActivity.setImage(bitmap, null); + } + + @Override + public void onBitmapFailed(Exception e, Drawable errorDrawable) { + errorToast(mMainActivity.getResources().getString(R.string.img_loading_error)); + if (!mMainActivity.isFinishing()) { + mMainActivity.setImage(null, null); + } + } + + @Override + public void onPrepareLoad(Drawable placeHolderDrawable) {} + }; + + void saveImage(final byte[] bytes) { + final Context appContext = mMainActivity.getApplicationContext(); + new Thread(new Runnable() { + @Override + public void run() { + try { + FileOutputStream outputStream = appContext.openFileOutput(FILE_NAME, Context.MODE_PRIVATE); + outputStream.write(bytes); + outputStream.flush(); + outputStream.close(); + } catch (Exception e) { + // + } + } + }).start(); + } + + void restoreImage() { + cancelRequest(); + restoreBytesTask = new RestoreBytesTask(mMainActivity); + restoreBytesTask.execute(); + } + + private static class RestoreBytesTask extends AsyncTask{ + + private final WeakReference mMainActivityRef; + + RestoreBytesTask(MainActivity mainActivity) { + mMainActivityRef = new WeakReference<>(mainActivity); + } + + @Override + protected byte[] doInBackground(Void... voids) { + try { + MainActivity activityRef = mMainActivityRef.get(); + if (activityRef != null && !activityRef.isFinishing()) { + FileInputStream inputStream = activityRef.openFileInput(FILE_NAME); + byte[] bytes = new byte[(int) inputStream.getChannel().size()]; + inputStream.read(bytes); + inputStream.close(); + activityRef.deleteFile(FILE_NAME); + return bytes; + } + } catch (Exception e) { + // + } + return null; + } + + @Override + protected void onPostExecute(byte[] bytes) { + super.onPostExecute(bytes); + MainActivity activityRef = mMainActivityRef.get(); + if (activityRef != null && !activityRef.isFinishing() && !isCancelled()) + activityRef.setImage(null, bytes); + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/MainActivity.java b/src/main/java/com/sharebounds/sharebounds/main/MainActivity.java new file mode 100755 index 0000000..8a023c7 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/MainActivity.java @@ -0,0 +1,261 @@ +package com.sharebounds.sharebounds.main; + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.hardware.SensorManager; +import android.net.Uri; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.support.v7.preference.PreferenceManager; +import android.view.View; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.OrientationListener; +import com.sharebounds.sharebounds.PermissionUtils; +import com.sharebounds.sharebounds.R; +import com.sharebounds.sharebounds.camera.ImageData; +import com.sharebounds.sharebounds.camera.OcrCameraController; + +public class MainActivity extends AppCompatActivity implements OrientationListener.OrientationCallBack, + OcrCameraController.OcrCameraListener, ActivityCompat.OnRequestPermissionsResultCallback { + + private static final int TEXT_REQUEST = 1; + private static final int THEME_REQUEST = 2; + private static final int PICK_IMAGE_REQUEST = 3; + + static final String CURRENT_TEXT = "currentText"; + private static final String STATE_TEXT = "stateText"; + private static final String STATE_IMAGE_DATA = "imageData"; + + private MainUIController mMainUIController; + private OcrCameraController mOcrCameraController; + private GalleryImageLoader mGalleryImageLoader; + private OrientationListener mOrientationListener; + + private String mText = ""; + private void setText(String text) { + mText = text; + mMainUIController.enableTextButton(!text.isEmpty(), false); + } + + private ImageData currentImageData; + void setImage(Bitmap bitmap, byte[] bytes) { + if (mIsLoadingImage && currentImageData != null) + mOcrCameraController.processImage(bitmap, bytes, currentImageData); + mIsLoadingImage = false; + currentImageData = null; + if (bytes != null) mGalleryImageLoader.restoreBytesTask = null; + } + private boolean mIsLoadingImage = false; + private boolean mPermissionRequested = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + PreferenceManager.setDefaultValues(this, R.xml.pref_settings, false); + setContentView(R.layout.activity_main); + + mOrientationListener = new OrientationListener(this, SensorManager.SENSOR_DELAY_NORMAL, + this); + mMainUIController = new MainUIController(this); + mGalleryImageLoader = new GalleryImageLoader(this); + mOcrCameraController = new OcrCameraController(this, mMainUIController.mainLayout); + + if (savedInstanceState == null) { + mMainUIController.enableTextButton(false, false); + + AppSettings.LaunchDialog launchDialog = AppSettings.getInstance(getApplicationContext()) + .getLaunchDialog(); + switch (launchDialog) { + case Tutorial: + BottomSheetFragment bottomSheetFragment = new BottomSheetFragment(); + bottomSheetFragment.show(getSupportFragmentManager(), bottomSheetFragment.getTag()); + break; + case Rate: + ShareUtils.showRateDialog(this); + break; + } + } + + if (!PermissionUtils.getPermission(this, PermissionUtils.Type.Camera)) { + mPermissionRequested = true; + PermissionUtils.requestPermission(this, PermissionUtils.Type.Camera); + } + + if (savedInstanceState != null) { + String text = savedInstanceState.getString(STATE_TEXT); + if (text != null) setText(text); + + ImageData imageData = savedInstanceState.getParcelable(STATE_IMAGE_DATA); + if (imageData != null) { + currentImageData = imageData; + mMainUIController.enableFlashButton(false, false); + mOcrCameraController.pauseCamera(true); + reloadBitmapFromUri(imageData.uri); + } + } + } + + private void reloadBitmapFromUri(final Uri uri) { + mIsLoadingImage = true; + if (uri == null) { + mGalleryImageLoader.restoreImage(); + } else { + mMainUIController.mainLayout.post(new Runnable() { + @Override + public void run() { + mGalleryImageLoader.loadSetImage(uri, mOcrCameraController.getImageViewSize()); + } + }); + } + } + + @Override + protected void onResume() { + super.onResume(); + if (mOrientationListener.canDetectOrientation()) { + mOrientationListener.enable(); + } + if (!mPermissionRequested) mOcrCameraController.resumeCamera(false); + mOcrCameraController.setup(); + } + + @Override + protected void onPause() { + super.onPause(); + release(false); + } + + private void release(boolean stop) { + mOrientationListener.disable(); + mOcrCameraController.pauseCamera(stop); + mOcrCameraController.release(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putString(STATE_TEXT, mText); + ImageData currentData = mOcrCameraController.getParcelableData(); + if (currentData != null) { + if (mOcrCameraController.getBytes() != null) { + mGalleryImageLoader.saveImage(mOcrCameraController.getBytes()); + } + outState.putParcelable(STATE_IMAGE_DATA, currentData); + } + super.onSaveInstanceState(outState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + release(true); + mIsLoadingImage = false; + mGalleryImageLoader.cancelRequest(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + switch (requestCode) { + case PermissionUtils.CAMERA_REQUEST: + mOcrCameraController.resumeCamera(true); + break; + case PermissionUtils.STORAGE_REQUEST: + showImagePicker(); + break; + } + } else { + PermissionUtils.errorToast(this, PermissionUtils.Type.getType(requestCode)); + } + mPermissionRequested = false; + } + + public void onRotation(final int oldRotation, final int newRotation) { + mMainUIController.mainLayout.post(new Runnable() { + @Override + public void run() { + mMainUIController.rotateImageView(oldRotation, newRotation); + mMainUIController.rotateButtons(oldRotation, newRotation); + mOcrCameraController.onRotation(newRotation); + } + }); + } + + @Override + public void onCameraEvent(boolean on) { + if (on && mIsLoadingImage){ + mGalleryImageLoader.cancelRequest(); + mIsLoadingImage = false; + currentImageData = null; + } + mMainUIController.enableEditUI(!on, !mText.isEmpty()); + } + + public void onNewOcrText(String text) { + setText(mText + text); + } + + public void cancelButtonClick(View view) { + mText = ""; + mMainUIController.enableTextButton(false, true); + } + + public void imagePickerIntent(View view) { + if (PermissionUtils.getPermission(this, PermissionUtils.Type.Storage)) { + showImagePicker(); + } else { + PermissionUtils.requestPermission(this, PermissionUtils.Type.Storage); + } + } + + private void showImagePicker() { + Intent imagePickerIntent = new Intent(Intent.ACTION_GET_CONTENT); + imagePickerIntent.setType("image/*"); + startActivityForResult( + Intent.createChooser(imagePickerIntent, + getString(R.string.image_picker_text)), PICK_IMAGE_REQUEST); + } + + public void textIntent(View view) { + Intent textIntent = new Intent(this, TextActivity.class); + textIntent.putExtra(CURRENT_TEXT, mText); + startActivityForResult(textIntent, TEXT_REQUEST); + } + + public void settingsIntent(View view) { + Intent settingsIntent = new Intent(this, SettingsActivity.class); + startActivityForResult(settingsIntent, THEME_REQUEST); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case (TEXT_REQUEST): + if (resultCode == RESULT_OK) { + setText(data.getStringExtra(TextActivity.INTENT_TEXT)); + } + break; + case (THEME_REQUEST): + final int newTheme = AppSettings.getInstance(getApplicationContext()).getTheme(); + if (mMainUIController.currentTheme != newTheme) { + mMainUIController.setTheme(newTheme); + mOcrCameraController.setTheme(newTheme); + } + break; + case (PICK_IMAGE_REQUEST): + if (resultCode == RESULT_OK && data != null && data.getData() != null) { + mOcrCameraController.pauseCamera(true); + Uri imageUri = data.getData(); + currentImageData = new ImageData(imageUri, 0, null); + mIsLoadingImage = true; + mGalleryImageLoader.loadSetImage(imageUri, mOcrCameraController.getImageViewSize()); + } + } + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/MainUIController.java b/src/main/java/com/sharebounds/sharebounds/main/MainUIController.java new file mode 100755 index 0000000..a02e9fd --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/MainUIController.java @@ -0,0 +1,121 @@ +package com.sharebounds.sharebounds.main; + +import android.support.constraint.ConstraintLayout; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.BaseThemeImageButton; +import com.sharebounds.sharebounds.R; +import com.sharebounds.sharebounds.camera.BitmapImageView; + +class MainUIController { + + int currentTheme; + ConstraintLayout mainLayout; + private BitmapImageView mBitmapImageView; + + private BaseThemeImageButton[] mButtons; + private BaseThemeImageButton mFlashButton, mTextButton, mCancelButton; + + MainUIController(AppCompatActivity activity) { + currentTheme = AppSettings.getInstance(activity.getApplicationContext()).getTheme(); + setupViews(activity); + } + + private void setupViews(AppCompatActivity activity) { + mBitmapImageView = activity.findViewById(R.id.camera_image_view); + mainLayout = activity.findViewById(R.id.main_layout); + mTextButton = activity.findViewById(R.id.text_button); + mCancelButton = activity.findViewById(R.id.cancel_button); + mFlashButton = activity.findViewById(R.id.flash_button); + + mButtons = new BaseThemeImageButton[]{ + mTextButton, mCancelButton, mFlashButton, + activity.findViewById(R.id.capture_button), + activity.findViewById(R.id.photos_button), + activity.findViewById(R.id.settings_button) + }; + } + + void setTheme(int theme) { + currentTheme = theme; + for (BaseThemeImageButton button: mButtons) { + button.setTheme(theme); + } + } + + void rotateButtons(int oldRotation, int newRotation) { + for (ImageButton button: mButtons) { + button.setRotation(oldRotation); + button.animate().rotation(newRotation).setDuration(300); + } + } + + void enableTextButton(boolean enabled, boolean animate) { + mTextButton.setEnabled(enabled); + mCancelButton.setEnabled(enabled); + if (animate) { + mTextButton.animate().alpha(enabled ? 1.0f : 0.5f).setDuration(300); + mCancelButton.animate().alpha(enabled ? 1.0f : 0.5f).setDuration(300); + } else { + mTextButton.setAlpha(enabled ? 1.0f : 0.5f); + mCancelButton.setAlpha(enabled ? 1.0f : 0.5f); + } + } + + void enableFlashButton(boolean enabled, boolean animate) { + if (enabled == mFlashButton.isEnabled()) return; + mFlashButton.setEnabled(enabled); + if (animate) { + if (enabled) { + mFlashButton.animate().alpha(1.0f).setDuration(300).withStartAction(new Runnable() { + @Override + public void run() { + mFlashButton.setVisibility(View.VISIBLE); + } + }); + } else { + mFlashButton.animate().alpha(0.0f).setDuration(300).withEndAction(new Runnable() { + @Override + public void run() { + mFlashButton.setVisibility(View.INVISIBLE); + } + }); + } + } + else { + mFlashButton.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); + mFlashButton.setAlpha(enabled ? 1.0f : 0.0f); + } + } + + void enableEditUI(boolean enabled, boolean textNotEmpty) { + enableTextButton(textNotEmpty, true); + enableFlashButton(!enabled, true); + } + + void rotateImageView(int oldRotation, int newRotation) { + mBitmapImageView.setRotation(oldRotation); + mBitmapImageView.animate().rotation(newRotation).setDuration(300); + + int w = mainLayout.getWidth(); + int h = mainLayout.getHeight(); + ViewGroup.LayoutParams layoutParams = mBitmapImageView.getLayoutParams(); + if (newRotation % 180 == 0) { + layoutParams.height = h; + layoutParams.width = w; + mBitmapImageView.setTranslationX(0); + mBitmapImageView.setTranslationY(0); + } else { + layoutParams.height = w; + layoutParams.width = h; + mBitmapImageView.setTranslationX((w - h) / 2); + mBitmapImageView.setTranslationY((h - w) / 2); + } + mBitmapImageView.setLayoutParams(layoutParams); + mBitmapImageView.requestLayout(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/PrivTouActivity.java b/src/main/java/com/sharebounds/sharebounds/main/PrivTouActivity.java new file mode 100755 index 0000000..96d618e --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/PrivTouActivity.java @@ -0,0 +1,49 @@ +package com.sharebounds.sharebounds.main; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.R; + +public class PrivTouActivity extends AppCompatActivity { + + private Button mButton; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(R.style.MainTheme); + super.onCreate(savedInstanceState); + + if (AppSettings.getInstance(getApplicationContext()).getTermsAccepted()) { + toMainActivity(); + } + + setContentView(R.layout.activity_priv_tou); + + mButton = findViewById(R.id.buttonTouPriv); + CheckBox mCheckBox = findViewById(R.id.checkBoxTouPriv); + mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mButton.setEnabled(isChecked); + } + }); + } + + public void buttonClicked(View view) { + AppSettings.getInstance(getApplicationContext()).setTermsAccepted(true); + toMainActivity(); + } + + private void toMainActivity() { + startActivity(new Intent(this, MainActivity.class)); + finish(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/SettingsActivity.java b/src/main/java/com/sharebounds/sharebounds/main/SettingsActivity.java new file mode 100755 index 0000000..4cf6761 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/SettingsActivity.java @@ -0,0 +1,32 @@ +package com.sharebounds.sharebounds.main; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; + +import com.sharebounds.sharebounds.R; + +public class SettingsActivity extends BaseThemeActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.activity_settings); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + finish(); + } + return super.onOptionsItemSelected(item); + } + + void reloadActivity(){ + Intent intent = getIntent(); + finish(); + startActivity(intent); + overridePendingTransition(0, 0); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/SettingsFragment.java b/src/main/java/com/sharebounds/sharebounds/main/SettingsFragment.java new file mode 100755 index 0000000..7a6816f --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/SettingsFragment.java @@ -0,0 +1,147 @@ +package com.sharebounds.sharebounds.main; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceFragmentCompat; + +import com.google.android.gms.oss.licenses.OssLicensesMenuActivity; +import com.sharebounds.sharebounds.R; + +public class SettingsFragment extends PreferenceFragmentCompat + implements SharedPreferences.OnSharedPreferenceChangeListener { + + private static final String DESIGN_LIST = "design_colors_list"; + private static final String KEYBOARD_LIST = "keyboard_globe_list"; + private static final String[] BUTTON_NAMES = {"app_share_button", "app_rate_button", "app_tut_button", + "app_tou_button", "app_priv_button", "app_info_button"}; + + private static final String PRIV_URL = "WEBSITE"; + private static final String TOU_URL = "WEBSITE"; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.pref_settings); + + setupListPreference(DESIGN_LIST); + setupListPreference(KEYBOARD_LIST); + setupPreferenceButtons(); + } + + private void setupListPreference(String key) { + SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); + ListPreference p = (ListPreference) findPreference(key); + setListPreferenceSummary(p, sharedPreferences.getString(key, "")); + } + + private void setListPreferenceSummary(ListPreference listPreference, String stringValue) { + int index = listPreference.findIndexOfValue(stringValue); + listPreference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null); + } + + private void setupPreferenceButtons() { + final SettingsActivity settingsActivity = (SettingsActivity) getActivity(); + if (settingsActivity == null) return; + + for (int i = 0; i < BUTTON_NAMES.length; i++) { + Preference.OnPreferenceClickListener onPreferenceClickListener = null; + switch (i) { + case 0: + onPreferenceClickListener = new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + ShareUtils.startShareIntent(settingsActivity, + getString(R.string.settings_share_app), true); + return true; + } + }; + break; + case 1: + onPreferenceClickListener = new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + ShareUtils.startRateIntent(settingsActivity); + return true; + } + }; + break; + case 2: + onPreferenceClickListener = new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + BottomSheetFragment bottomSheetFragment = new BottomSheetFragment(); + bottomSheetFragment.show(settingsActivity.getSupportFragmentManager(), + bottomSheetFragment.getTag()); + return true; + } + }; + break; + case 3: + onPreferenceClickListener = new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + settingsActivity.startActivity( + new Intent(Intent.ACTION_VIEW, Uri.parse(TOU_URL))); + return true; + } + }; + break; + case 4: + onPreferenceClickListener = new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + settingsActivity.startActivity( + new Intent(Intent.ACTION_VIEW, Uri.parse(PRIV_URL))); + return true; + } + }; + break; + case 5: + onPreferenceClickListener = new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + String title = getString(R.string.pref_title_info); + Intent ossIntent = new Intent(settingsActivity, OssLicensesMenuActivity.class); + ossIntent.putExtra("title", title); + startActivity(ossIntent); + return true; + } + }; + break; + } + findPreference(BUTTON_NAMES[i]).setOnPreferenceClickListener(onPreferenceClickListener); + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + Preference preference = findPreference(key); + if (preference != null) { + if (key.equals(DESIGN_LIST)) { + SettingsActivity settingsActivity = (SettingsActivity) getActivity(); + if (settingsActivity != null) { + settingsActivity.reloadActivity(); + } + } + else if (preference instanceof ListPreference) { + ListPreference listPreference = (ListPreference) preference; + setListPreferenceSummary(listPreference, sharedPreferences.getString(key, "")); + } + } + } + + @Override + public void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/ShareUtils.java b/src/main/java/com/sharebounds/sharebounds/main/ShareUtils.java new file mode 100755 index 0000000..b6ee904 --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/ShareUtils.java @@ -0,0 +1,96 @@ +package com.sharebounds.sharebounds.main; + +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; + +import com.sharebounds.sharebounds.AppSettings; +import com.sharebounds.sharebounds.R; + +class ShareUtils { + + private static final String MARKET_URI = "market://details?id="; + private static final String PLAY_URI = "https://play.google.com/store/apps/details?id="; + + static void startShareIntent(final Context context, String text) { + startShareIntent(context, text, false); + } + + static void startShareIntent(final Context context, String text, boolean settings) { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.setType("text/plain"); + + if (!settings) { + text = context.getString(R.string.share_app_name) + text; + } + + shareIntent.putExtra(Intent.EXTRA_TEXT, text); + context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_title))); + } + + static void startRateIntent(final Context context) { + String packageName = context.getPackageName(); + + Uri uri = Uri.parse(MARKET_URI + packageName); + Intent goToMarket = new Intent(Intent.ACTION_VIEW, uri); + if (Build.VERSION.SDK_INT >= 21) + goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_NEW_DOCUMENT | + Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + else goToMarket.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | + Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + try { + context.startActivity(goToMarket); + } catch (ActivityNotFoundException e) { + context.startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse(PLAY_URI + packageName))); + } + } + + static void showRateDialog(final Context context) { + int theme = AppSettings.getInstance(context.getApplicationContext()).getTheme(); + final int textColor; + switch (theme) { + case 1: + textColor = context.getResources().getColor(R.color.colorDarkAccent); + break; + default: + textColor = context.getResources().getColor(R.color.colorAccent); + } + + final AlertDialog dialog = new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.rate_title)) + .setMessage(context.getString(R.string.rate_body)) + .setPositiveButton(context.getString(R.string.rate_button_pos), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int i) { + startRateIntent(context); + } + }) + .setNeutralButton(context.getString(R.string.rate_button_later), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + AppSettings.getInstance(context.getApplicationContext()).newRateDialogCount(1); + } + }) + .setNegativeButton(context.getString(R.string.rate_button_no), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + AppSettings.getInstance(context.getApplicationContext()).newRateDialogCount(-1); + } + }).create(); + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogInterface) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(textColor); + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(textColor); + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(textColor); + } + }); + dialog.show(); + } +} diff --git a/src/main/java/com/sharebounds/sharebounds/main/TextActivity.java b/src/main/java/com/sharebounds/sharebounds/main/TextActivity.java new file mode 100755 index 0000000..2ff0c7e --- /dev/null +++ b/src/main/java/com/sharebounds/sharebounds/main/TextActivity.java @@ -0,0 +1,140 @@ +package com.sharebounds.sharebounds.main; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import com.sharebounds.sharebounds.R; + +public class TextActivity extends BaseThemeActivity { + + static final String INTENT_TEXT = "textActivityText"; + + private TextInputEditText mTextField; + private MenuItem mShareButton; + private boolean mDidScroll = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_text); + + mTextField = findViewById(R.id.textField); + + if (savedInstanceState == null) { + TextInputLayout textInputLayout = findViewById(R.id.textLayout); + textInputLayout.setHintAnimationEnabled(false); + String text = getIntent().getStringExtra(MainActivity.CURRENT_TEXT); + mTextField.setText(text); + mTextField.clearFocus(); + textInputLayout.setHintAnimationEnabled(true); + } + + setupListeners(); + } + + private void setupListeners() { + mTextField.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + shareButtonStatus(charSequence); + } + @Override + public void afterTextChanged(Editable editable) { + } + }); + + findViewById(R.id.textScroll).setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + scrollTouchHandleKeyboard(motionEvent); + if (motionEvent.getAction() == MotionEvent.ACTION_UP && !mDidScroll) view.performClick(); + return false; + } + }); + } + + private void shareButtonStatus(CharSequence charSequence) { + boolean empty = TextUtils.isEmpty(charSequence.toString().trim()); + if (mShareButton != null && (mShareButton.isEnabled() == empty)) { + enableShareButton(!empty); + } + } + + private void enableShareButton(boolean bool) { + mShareButton.setEnabled(bool); + mShareButton.getIcon().setAlpha(bool ? 255 : 130); + } + + private void scrollTouchHandleKeyboard(MotionEvent motionEvent) { + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_SCROLL: + mDidScroll = true; + break; + case MotionEvent.ACTION_UP: + if (!mDidScroll) { + hideKeyboard(); + } else { + mDidScroll = false; + } + } + } + + private void hideKeyboard() { + InputMethodManager input = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (input != null) { + input.hideSoftInputFromWindow(mTextField.getWindowToken(), 0); + mTextField.clearFocus(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.actionbar_share, menu); + mShareButton = menu.findItem(R.id.menu_item_share); + enableShareButton(!TextUtils.isEmpty(mTextField.getText().toString().trim())); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_item_share: + ShareUtils.startShareIntent(this, mTextField.getText().toString().trim()); + mTextField.clearFocus(); + return true; + case android.R.id.home: + saveResult(); + finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void saveResult() { + Intent resultIntent = new Intent(); + resultIntent.putExtra(INTENT_TEXT, mTextField.getText().toString()); + setResult(RESULT_OK, resultIntent); + } + + @Override + public void onBackPressed() { + saveResult(); + super.onBackPressed(); + } +} diff --git a/src/main/res/anim/shutter.xml b/src/main/res/anim/shutter.xml new file mode 100755 index 0000000..51efd1c --- /dev/null +++ b/src/main/res/anim/shutter.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/src/main/res/drawable-hdpi/ic_backspace_black_24dp.png b/src/main/res/drawable-hdpi/ic_backspace_black_24dp.png new file mode 100755 index 0000000..11e6bc4 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_backspace_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_close_black_24dp.png b/src/main/res/drawable-hdpi/ic_close_black_24dp.png new file mode 100755 index 0000000..1a9cd75 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_close_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_flash_auto_black_24dp.png b/src/main/res/drawable-hdpi/ic_flash_auto_black_24dp.png new file mode 100755 index 0000000..95ba465 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_flash_auto_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_flash_off_black_24dp.png b/src/main/res/drawable-hdpi/ic_flash_off_black_24dp.png new file mode 100755 index 0000000..8908622 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_flash_off_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_flash_on_black_24dp.png b/src/main/res/drawable-hdpi/ic_flash_on_black_24dp.png new file mode 100755 index 0000000..5921a0e Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_flash_on_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_fullscreen_black_24dp.png b/src/main/res/drawable-hdpi/ic_fullscreen_black_24dp.png new file mode 100755 index 0000000..57ff4f0 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_fullscreen_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_fullscreen_exit_black_24dp.png b/src/main/res/drawable-hdpi/ic_fullscreen_exit_black_24dp.png new file mode 100755 index 0000000..8328e2e Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_fullscreen_exit_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_keyboard_return_black_24dp.png b/src/main/res/drawable-hdpi/ic_keyboard_return_black_24dp.png new file mode 100755 index 0000000..783b2a8 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_keyboard_return_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_language_black_24dp.png b/src/main/res/drawable-hdpi/ic_language_black_24dp.png new file mode 100755 index 0000000..3612556 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_language_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_photo_library_black_24dp.png b/src/main/res/drawable-hdpi/ic_photo_library_black_24dp.png new file mode 100755 index 0000000..43b8cd2 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_photo_library_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_settings_black_24dp.png b/src/main/res/drawable-hdpi/ic_settings_black_24dp.png new file mode 100755 index 0000000..acf1ddf Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_settings_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_share_black_24dp.png b/src/main/res/drawable-hdpi/ic_share_black_24dp.png new file mode 100755 index 0000000..20ba480 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_share_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_space_bar_black_24dp.png b/src/main/res/drawable-hdpi/ic_space_bar_black_24dp.png new file mode 100755 index 0000000..44caca5 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_space_bar_black_24dp.png differ diff --git a/src/main/res/drawable-hdpi/ic_text_fields_black_24dp.png b/src/main/res/drawable-hdpi/ic_text_fields_black_24dp.png new file mode 100755 index 0000000..3809ef2 Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_text_fields_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_backspace_black_24dp.png b/src/main/res/drawable-mdpi/ic_backspace_black_24dp.png new file mode 100755 index 0000000..0068a9c Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_backspace_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_close_black_24dp.png b/src/main/res/drawable-mdpi/ic_close_black_24dp.png new file mode 100755 index 0000000..40a1a84 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_close_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_flash_auto_black_24dp.png b/src/main/res/drawable-mdpi/ic_flash_auto_black_24dp.png new file mode 100755 index 0000000..636a4ec Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_flash_auto_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_flash_off_black_24dp.png b/src/main/res/drawable-mdpi/ic_flash_off_black_24dp.png new file mode 100755 index 0000000..4367ecb Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_flash_off_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_flash_on_black_24dp.png b/src/main/res/drawable-mdpi/ic_flash_on_black_24dp.png new file mode 100755 index 0000000..a621a70 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_flash_on_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_fullscreen_black_24dp.png b/src/main/res/drawable-mdpi/ic_fullscreen_black_24dp.png new file mode 100755 index 0000000..3553d6a Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_fullscreen_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_fullscreen_exit_black_24dp.png b/src/main/res/drawable-mdpi/ic_fullscreen_exit_black_24dp.png new file mode 100755 index 0000000..c839448 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_fullscreen_exit_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_keyboard_return_black_24dp.png b/src/main/res/drawable-mdpi/ic_keyboard_return_black_24dp.png new file mode 100755 index 0000000..3e015a3 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_keyboard_return_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_language_black_24dp.png b/src/main/res/drawable-mdpi/ic_language_black_24dp.png new file mode 100755 index 0000000..62ef88c Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_language_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_photo_library_black_24dp.png b/src/main/res/drawable-mdpi/ic_photo_library_black_24dp.png new file mode 100755 index 0000000..6fb2ad6 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_photo_library_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_settings_black_24dp.png b/src/main/res/drawable-mdpi/ic_settings_black_24dp.png new file mode 100755 index 0000000..c59419c Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_settings_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_share_black_24dp.png b/src/main/res/drawable-mdpi/ic_share_black_24dp.png new file mode 100755 index 0000000..f02d360 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_share_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_space_bar_black_24dp.png b/src/main/res/drawable-mdpi/ic_space_bar_black_24dp.png new file mode 100755 index 0000000..042f237 Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_space_bar_black_24dp.png differ diff --git a/src/main/res/drawable-mdpi/ic_text_fields_black_24dp.png b/src/main/res/drawable-mdpi/ic_text_fields_black_24dp.png new file mode 100755 index 0000000..a3f3c6c Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_text_fields_black_24dp.png differ diff --git a/src/main/res/drawable-v21/capture_button.xml b/src/main/res/drawable-v21/capture_button.xml new file mode 100755 index 0000000..d2ce28c --- /dev/null +++ b/src/main/res/drawable-v21/capture_button.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable-v21/capture_button_dark.xml b/src/main/res/drawable-v21/capture_button_dark.xml new file mode 100755 index 0000000..d2cea88 --- /dev/null +++ b/src/main/res/drawable-v21/capture_button_dark.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable-v21/round_button.xml b/src/main/res/drawable-v21/round_button.xml new file mode 100755 index 0000000..fe5ee01 --- /dev/null +++ b/src/main/res/drawable-v21/round_button.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable-v21/round_button_dark.xml b/src/main/res/drawable-v21/round_button_dark.xml new file mode 100755 index 0000000..45ccbbe --- /dev/null +++ b/src/main/res/drawable-v21/round_button_dark.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable-xhdpi/ic_backspace_black_24dp.png b/src/main/res/drawable-xhdpi/ic_backspace_black_24dp.png new file mode 100755 index 0000000..bee6530 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_backspace_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_close_black_24dp.png b/src/main/res/drawable-xhdpi/ic_close_black_24dp.png new file mode 100755 index 0000000..6bc4372 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_close_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_flash_auto_black_24dp.png b/src/main/res/drawable-xhdpi/ic_flash_auto_black_24dp.png new file mode 100755 index 0000000..050fae7 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_flash_auto_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_flash_off_black_24dp.png b/src/main/res/drawable-xhdpi/ic_flash_off_black_24dp.png new file mode 100755 index 0000000..70ba9d2 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_flash_off_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_flash_on_black_24dp.png b/src/main/res/drawable-xhdpi/ic_flash_on_black_24dp.png new file mode 100755 index 0000000..44a7f41 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_flash_on_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_fullscreen_black_24dp.png b/src/main/res/drawable-xhdpi/ic_fullscreen_black_24dp.png new file mode 100755 index 0000000..917e418 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_fullscreen_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_fullscreen_exit_black_24dp.png b/src/main/res/drawable-xhdpi/ic_fullscreen_exit_black_24dp.png new file mode 100755 index 0000000..5fc3166 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_fullscreen_exit_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_keyboard_return_black_24dp.png b/src/main/res/drawable-xhdpi/ic_keyboard_return_black_24dp.png new file mode 100755 index 0000000..fef0473 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_keyboard_return_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_language_black_24dp.png b/src/main/res/drawable-xhdpi/ic_language_black_24dp.png new file mode 100755 index 0000000..5ed4a92 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_language_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_photo_library_black_24dp.png b/src/main/res/drawable-xhdpi/ic_photo_library_black_24dp.png new file mode 100755 index 0000000..5ab220f Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_photo_library_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_settings_black_24dp.png b/src/main/res/drawable-xhdpi/ic_settings_black_24dp.png new file mode 100755 index 0000000..e84e188 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_settings_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_share_black_24dp.png b/src/main/res/drawable-xhdpi/ic_share_black_24dp.png new file mode 100755 index 0000000..81c80b7 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_share_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_space_bar_black_24dp.png b/src/main/res/drawable-xhdpi/ic_space_bar_black_24dp.png new file mode 100755 index 0000000..cb846be Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_space_bar_black_24dp.png differ diff --git a/src/main/res/drawable-xhdpi/ic_text_fields_black_24dp.png b/src/main/res/drawable-xhdpi/ic_text_fields_black_24dp.png new file mode 100755 index 0000000..44c3710 Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_text_fields_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_backspace_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_backspace_black_24dp.png new file mode 100755 index 0000000..57e2640 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_backspace_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_close_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_close_black_24dp.png new file mode 100755 index 0000000..51b4401 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_close_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_flash_auto_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_flash_auto_black_24dp.png new file mode 100755 index 0000000..a220821 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_flash_auto_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_flash_off_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_flash_off_black_24dp.png new file mode 100755 index 0000000..c1fb7a7 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_flash_off_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_flash_on_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_flash_on_black_24dp.png new file mode 100755 index 0000000..8942563 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_flash_on_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_fullscreen_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_fullscreen_black_24dp.png new file mode 100755 index 0000000..66a373c Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_fullscreen_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_black_24dp.png new file mode 100755 index 0000000..5691b55 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_fullscreen_exit_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_keyboard_return_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_keyboard_return_black_24dp.png new file mode 100755 index 0000000..254d9df Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_keyboard_return_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_language_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_language_black_24dp.png new file mode 100755 index 0000000..68608c7 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_language_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_photo_library_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_photo_library_black_24dp.png new file mode 100755 index 0000000..bd91f66 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_photo_library_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_settings_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_settings_black_24dp.png new file mode 100755 index 0000000..3023ff8 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_settings_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png new file mode 100755 index 0000000..784933a Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_space_bar_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_space_bar_black_24dp.png new file mode 100755 index 0000000..a0b063e Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_space_bar_black_24dp.png differ diff --git a/src/main/res/drawable-xxhdpi/ic_text_fields_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_text_fields_black_24dp.png new file mode 100755 index 0000000..52a6686 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/ic_text_fields_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_backspace_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_backspace_black_24dp.png new file mode 100755 index 0000000..22fda61 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_backspace_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_close_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_close_black_24dp.png new file mode 100755 index 0000000..df42fee Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_close_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_flash_auto_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_flash_auto_black_24dp.png new file mode 100755 index 0000000..243324b Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_flash_auto_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_flash_off_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_flash_off_black_24dp.png new file mode 100755 index 0000000..dd055bd Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_flash_off_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_flash_on_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_flash_on_black_24dp.png new file mode 100755 index 0000000..75b8b01 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_flash_on_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_fullscreen_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_fullscreen_black_24dp.png new file mode 100755 index 0000000..9feee98 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_fullscreen_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_black_24dp.png new file mode 100755 index 0000000..2221235 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_fullscreen_exit_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_keyboard_return_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_keyboard_return_black_24dp.png new file mode 100755 index 0000000..2a69e2e Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_keyboard_return_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_language_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_language_black_24dp.png new file mode 100755 index 0000000..48997ba Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_language_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_photo_library_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_photo_library_black_24dp.png new file mode 100755 index 0000000..73ac084 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_photo_library_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_settings_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_settings_black_24dp.png new file mode 100755 index 0000000..476d5c9 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_settings_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png new file mode 100755 index 0000000..5a8544c Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_space_bar_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_space_bar_black_24dp.png new file mode 100755 index 0000000..67cd948 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_space_bar_black_24dp.png differ diff --git a/src/main/res/drawable-xxxhdpi/ic_text_fields_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_text_fields_black_24dp.png new file mode 100755 index 0000000..724e325 Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/ic_text_fields_black_24dp.png differ diff --git a/src/main/res/drawable/capture_button.xml b/src/main/res/drawable/capture_button.xml new file mode 100755 index 0000000..daaff67 --- /dev/null +++ b/src/main/res/drawable/capture_button.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/capture_button_dark.xml b/src/main/res/drawable/capture_button_dark.xml new file mode 100755 index 0000000..a1668c4 --- /dev/null +++ b/src/main/res/drawable/capture_button_dark.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/capture_button_dark_normal.xml b/src/main/res/drawable/capture_button_dark_normal.xml new file mode 100755 index 0000000..ae5c3e9 --- /dev/null +++ b/src/main/res/drawable/capture_button_dark_normal.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/capture_button_dark_selected.xml b/src/main/res/drawable/capture_button_dark_selected.xml new file mode 100755 index 0000000..6eb24aa --- /dev/null +++ b/src/main/res/drawable/capture_button_dark_selected.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/capture_button_normal.xml b/src/main/res/drawable/capture_button_normal.xml new file mode 100755 index 0000000..217298c --- /dev/null +++ b/src/main/res/drawable/capture_button_normal.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/capture_button_selected.xml b/src/main/res/drawable/capture_button_selected.xml new file mode 100755 index 0000000..704ee55 --- /dev/null +++ b/src/main/res/drawable/capture_button_selected.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/round_button.xml b/src/main/res/drawable/round_button.xml new file mode 100755 index 0000000..27d2ab2 --- /dev/null +++ b/src/main/res/drawable/round_button.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/round_button_dark.xml b/src/main/res/drawable/round_button_dark.xml new file mode 100755 index 0000000..7bda1a8 --- /dev/null +++ b/src/main/res/drawable/round_button_dark.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/round_button_dark_normal.xml b/src/main/res/drawable/round_button_dark_normal.xml new file mode 100755 index 0000000..54e7ad1 --- /dev/null +++ b/src/main/res/drawable/round_button_dark_normal.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/round_button_dark_selected.xml b/src/main/res/drawable/round_button_dark_selected.xml new file mode 100755 index 0000000..1ae2a93 --- /dev/null +++ b/src/main/res/drawable/round_button_dark_selected.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/round_button_normal.xml b/src/main/res/drawable/round_button_normal.xml new file mode 100755 index 0000000..246a7b8 --- /dev/null +++ b/src/main/res/drawable/round_button_normal.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/round_button_selected.xml b/src/main/res/drawable/round_button_selected.xml new file mode 100755 index 0000000..6ba055e --- /dev/null +++ b/src/main/res/drawable/round_button_selected.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/res/drawable/share_button_white.xml b/src/main/res/drawable/share_button_white.xml new file mode 100755 index 0000000..dbb66ed --- /dev/null +++ b/src/main/res/drawable/share_button_white.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/src/main/res/drawable/splash_image.xml b/src/main/res/drawable/splash_image.xml new file mode 100755 index 0000000..ab03c16 --- /dev/null +++ b/src/main/res/drawable/splash_image.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/layout/activity_main.xml b/src/main/res/layout/activity_main.xml new file mode 100755 index 0000000..b7c7683 --- /dev/null +++ b/src/main/res/layout/activity_main.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/res/layout/activity_priv_tou.xml b/src/main/res/layout/activity_priv_tou.xml new file mode 100755 index 0000000..21a437f --- /dev/null +++ b/src/main/res/layout/activity_priv_tou.xml @@ -0,0 +1,41 @@ + + + + + + + + + +