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
+
+
+
+(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 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/layout/activity_settings.xml b/src/main/res/layout/activity_settings.xml
new file mode 100755
index 0000000..800d9eb
--- /dev/null
+++ b/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/src/main/res/layout/activity_text.xml b/src/main/res/layout/activity_text.xml
new file mode 100755
index 0000000..e68a454
--- /dev/null
+++ b/src/main/res/layout/activity_text.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/res/layout/fragment_bottom_sheet.xml b/src/main/res/layout/fragment_bottom_sheet.xml
new file mode 100755
index 0000000..2590019
--- /dev/null
+++ b/src/main/res/layout/fragment_bottom_sheet.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/res/layout/keyboard.xml b/src/main/res/layout/keyboard.xml
new file mode 100755
index 0000000..12024b2
--- /dev/null
+++ b/src/main/res/layout/keyboard.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/menu/actionbar_share.xml b/src/main/res/menu/actionbar_share.xml
new file mode 100755
index 0000000..3de7c0b
--- /dev/null
+++ b/src/main/res/menu/actionbar_share.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100755
index 0000000..67820c5
--- /dev/null
+++ b/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100755
index 0000000..67820c5
--- /dev/null
+++ b/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/mipmap-hdpi/ic_launcher.png b/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..de9eba9
Binary files /dev/null and b/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100755
index 0000000..2db0083
Binary files /dev/null and b/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/src/main/res/mipmap-hdpi/ic_launcher_round.png b/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100755
index 0000000..4988562
Binary files /dev/null and b/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/src/main/res/mipmap-mdpi/ic_launcher.png b/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..bc7d86b
Binary files /dev/null and b/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100755
index 0000000..caaf3c1
Binary files /dev/null and b/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/src/main/res/mipmap-mdpi/ic_launcher_round.png b/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100755
index 0000000..8aae1e5
Binary files /dev/null and b/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/src/main/res/mipmap-xhdpi/ic_launcher.png b/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..b81fefa
Binary files /dev/null and b/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100755
index 0000000..7c25f1d
Binary files /dev/null and b/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100755
index 0000000..12d77d6
Binary files /dev/null and b/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..7d53fb1
Binary files /dev/null and b/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100755
index 0000000..8688a3c
Binary files /dev/null and b/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100755
index 0000000..28986b3
Binary files /dev/null and b/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..525bd74
Binary files /dev/null and b/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100755
index 0000000..3bb79a2
Binary files /dev/null and b/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100755
index 0000000..b34ef7a
Binary files /dev/null and b/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml
new file mode 100755
index 0000000..3f41033
--- /dev/null
+++ b/src/main/res/values/arrays.xml
@@ -0,0 +1,21 @@
+
+
+
+ - Default
+ - Dark
+
+
+ - 0
+ - 1
+
+
+ - Next Keyboard
+ - Previous Keyboard
+ - Keyboard Picker
+
+
+ - 0
+ - 1
+ - 2
+
+
\ No newline at end of file
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
new file mode 100755
index 0000000..7a6e4ae
--- /dev/null
+++ b/src/main/res/values/colors.xml
@@ -0,0 +1,13 @@
+
+
+ #1b57e6
+ #0044ce
+ #FF4081
+ #37474f
+ #102027
+ #ff6e40
+ #263238
+
+ #DE000000
+ #8A000000
+
diff --git a/src/main/res/values/dimens.xml b/src/main/res/values/dimens.xml
new file mode 100755
index 0000000..18b2f52
--- /dev/null
+++ b/src/main/res/values/dimens.xml
@@ -0,0 +1,14 @@
+
+
+ 56dp
+ 56dp
+ 50dp
+ 3dp
+ 2dp
+ 48dp
+ 40dp
+ 40dp
+
+ 16dp
+ 8dp
+
diff --git a/src/main/res/values/ic_launcher_background.xml b/src/main/res/values/ic_launcher_background.xml
new file mode 100755
index 0000000..6ee67f0
--- /dev/null
+++ b/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #1B57E6
+
\ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
new file mode 100755
index 0000000..43dd625
--- /dev/null
+++ b/src/main/res/values/strings.xml
@@ -0,0 +1,77 @@
+
+ Shrbounds
+ Sharebounds
+ Sharebounds:\n
+ Privacy Policy & Terms of Use
+ Text
+ Settings
+ Share text
+ Share
+
+ Open the main application to accept the Privacy P. & ToU
+ Terms of Use: WEBSITE \n
+ Privacy Policy: WEBSITE \n
+ I accept the Terms of Use and Privacy Policy
+ Continue
+
+ Couldn\'t load Mobile Vision Text (Google Play Services),
+ check if device storage low/ update Google Play Services.
+ Couldn\'t load image
+ Camera permission denied
+ Storage permission denied
+
+ Select image
+ Text image
+ Capture image button
+ Image picker button
+ Open text button
+ Flash mode button
+ Open settings button
+ Cancel button
+ Add button
+ Switch to next keyboard
+ Space button
+ Backspace button
+ Return button
+ Change keyboard size button
+
+ Buy Pro
+ Design
+ Like the App?
+ Keyboard
+ Information
+
+ Share via
+
+ No \'Sharebounds:\' in shared text
+ Rate
+ Share
+
+ Color Theme
+ Default
+
+ Globe Button
+ Next Keyboard
+ Sound
+ System Volume (Touch sounds)
+ Vibration
+
+ Tutorial
+ Terms of Use
+ Privacy Policy
+ Open Source Licenses
+
+ Download \'Sharebounds\': PLAYSTORE-URL
+ Rate Sharebounds
+ If you like this app, please leave a review on the Play Store.
+ Rate
+ Maybe later
+ No, thanks
+
+ Camera
+
+ •\t Capture an image\n•\t Get text:\n\t\t Single Tap: Word\n\t\t Double Tap: Line\n\t\t Long Press: Block\n•\t Share\n\n•\t Always verify the results and never capture/ scan/ share any private/ personal or confidential/ important text data!
+ Keyboard/ Device Settings
+ •\t System > Languages & input > Virtual keyboard > Manage keyboards > Enable Sharebounds: Camera\n\n•\t Camera permission: Apps & notifications > App permissions > Camera > Enable Sharebounds
+ •\t Google Play Services required (Mobile Vision: Text Recognition)
+
diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml
new file mode 100755
index 0000000..cfa83b9
--- /dev/null
+++ b/src/main/res/values/styles.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/res/xml/method.xml b/src/main/res/xml/method.xml
new file mode 100755
index 0000000..eda8b16
--- /dev/null
+++ b/src/main/res/xml/method.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/xml/pref_settings.xml b/src/main/res/xml/pref_settings.xml
new file mode 100755
index 0000000..ad589fc
--- /dev/null
+++ b/src/main/res/xml/pref_settings.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file