Initial commit

This commit is contained in:
fabio-4 2020-06-29 23:37:21 +02:00
commit 030892f013
169 changed files with 4609 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.DS_Store

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# Sharebounds: OCR - Keyboard
![Header](screenshots/header.png)
(Built in 2018)
## Screenshots
<p align="center">
<img src="screenshots/1.png" width="40%"/>
<img src="screenshots/2.png" width="40%"/>
</p>
<p align="center">
<img src="screenshots/3.png" width="40%" />
<img src="screenshots/4.png" width="40%" />
</p>

39
build.gradle Executable file
View File

@ -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'
}

BIN
screenshots/1.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

BIN
screenshots/2.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 KiB

BIN
screenshots/3.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
screenshots/4.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

BIN
screenshots/header.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

77
src/main/AndroidManifest.xml Executable file
View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sharebounds.sharebounds">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.hardware.camera.flash" android:required="false"/>
<supports-screens android:smallScreens="false"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:requiresSmallestWidthDp="240"/>
<application
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme"
android:resizeableActivity="false"
android:supportsPictureInPicture="false">
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.gms.vision.DEPENDENCIES"
android:value="ocr" />
<activity
android:name=".main.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/MainTheme">
</activity>
<activity
android:name=".main.PrivTouActivity"
android:label="@string/display_name"
android:screenOrientation="portrait"
android:theme="@style/MainTheme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".main.TextActivity"
android:label="@string/title_activity_text"
android:windowSoftInputMode="adjustResize">
</activity>
<activity
android:name=".main.SettingsActivity"
android:label="@string/title_activity_settings">
</activity>
<activity android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
<activity android:name="com.google.android.gms.oss.licenses.OssLicensesActivity"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"/>
<service android:name=".keyboard.CameraIME"
android:label="@string/app_name"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im"
android:resource="@xml/method" />
</service>
</application>
</manifest>

BIN
src/main/ic_launcher-web.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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<Integer, Integer> 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);
}
});
}
}

View File

@ -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<RectF> lines) {
Canvas canvas = new Canvas(imageBitmap);
for (RectF line: lines) {
float round = line.height() / 8;
canvas.drawRoundRect(line, round, round, mPaint);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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 <T> List<T> intersection(List<T> list1, List<T> list2) {
Set<T> set = new HashSet<>();
for (T t : list1) {
if (list2.contains(t)) {
set.add(t);
}
}
return new ArrayList<>(set);
}
static Camera.Size chooseOptimalSize(List<Camera.Size> 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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<ImageData> CREATOR = new Parcelable.Creator<ImageData>() {
public ImageData createFromParcel(Parcel in) {
return new ImageData(in);
}
public ImageData[] newArray(int size) {
return new ImageData[size];
}
};
@Override
public int describeContents() {
return 0;
}
}

View File

@ -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<Integer, Integer> 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;
}
}
}

View File

@ -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<TextBlock> mTextBlocks;
ImageModel(Bitmap bitmap, byte[] bytes, ImageData imageData) {
this.bitmap = bitmap;
this.bytes = bytes;
this.imageData = imageData;
}
void setTextBlocks(SparseArray<TextBlock> textBlocks) {
this.mTextBlocks = textBlocks;
}
List<RectF> getTextLines() {
float fixRotation = imageData.fixRotation;
Matrix matrix = CameraUtils.fixRotationMatrix(fixRotation, bitmap.getWidth(), bitmap.getHeight());
List<RectF> 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;
}
}

View File

@ -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<String> 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<Camera.Size> previewSizes = params.getSupportedPreviewSizes();
List<Camera.Size> pictureSizes = params.getSupportedPictureSizes();
List<Camera.Size> 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<String> 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;
}
}
}
}

View File

@ -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<Integer, Integer> 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<Integer, Integer> 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();
}
}

View File

@ -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);
}
}

View File

@ -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<TextBlock> 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;
}
}

View File

@ -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<Float, Void, ImageModel> {
private final WeakReference<OcrTask> 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<RectF> 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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
});
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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<Integer, Integer> 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<Void, Void, byte[]>{
private final WeakReference<MainActivity> 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);
}
}
}

View File

@ -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());
}
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

14
src/main/res/anim/shutter.xml Executable file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha="0.0"
android:toAlpha="0.9"
android:duration="75"
android:interpolator="@android:anim/decelerate_interpolator"/>
<alpha
android:fromAlpha="0.9"
android:toAlpha="0.0"
android:duration="75"
android:startOffset="75"
android:interpolator="@android:anim/accelerate_interpolator"/>
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="@android:color/black" />
<size android:width="@dimen/captureButtonInner"
android:height="@dimen/captureButtonInner"/>
</shape>
</item>
<item android:drawable="@drawable/capture_button_normal"/>
</ripple>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="@android:color/black" />
<size android:width="@dimen/captureButtonInner"
android:height="@dimen/captureButtonInner"/>
</shape>
</item>
<item android:drawable="@drawable/capture_button_dark_normal"/>
</ripple>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="@android:color/black" />
<size android:width="@dimen/buttonWidth" android:height="@dimen/buttonHeight" />
</shape>
</item>
<item android:drawable="@drawable/round_button_normal"/>
</ripple>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="@android:color/black" />
<size android:width="@dimen/buttonWidth" android:height="@dimen/buttonHeight" />
</shape>
</item>
<item android:drawable="@drawable/round_button_dark_normal"/>
</ripple>

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Some files were not shown because too many files have changed in this diff Show More