Initial commit
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.DS_Store
|
16
README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Sharebounds: OCR - Keyboard
|
||||
|
||||

|
||||
|
||||
(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
@ -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
After Width: | Height: | Size: 296 KiB |
BIN
screenshots/2.png
Executable file
After Width: | Height: | Size: 891 KiB |
BIN
screenshots/3.png
Executable file
After Width: | Height: | Size: 72 KiB |
BIN
screenshots/4.png
Executable file
After Width: | Height: | Size: 602 KiB |
BIN
screenshots/header.png
Executable file
After Width: | Height: | Size: 151 KiB |
77
src/main/AndroidManifest.xml
Executable 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
After Width: | Height: | Size: 21 KiB |
97
src/main/java/com/sharebounds/sharebounds/AppSettings.java
Executable 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();
|
||||
}
|
||||
}
|
36
src/main/java/com/sharebounds/sharebounds/BaseThemeImageButton.java
Executable 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();
|
||||
}
|
||||
}
|
101
src/main/java/com/sharebounds/sharebounds/OrientationListener.java
Executable 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;
|
||||
}
|
||||
}
|
55
src/main/java/com/sharebounds/sharebounds/PermissionUtils.java
Executable 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();
|
||||
}
|
||||
}
|
150
src/main/java/com/sharebounds/sharebounds/camera/BitmapImageView.java
Executable 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
90
src/main/java/com/sharebounds/sharebounds/camera/BitmapUtils.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
38
src/main/java/com/sharebounds/sharebounds/camera/CameraAnimationView.java
Executable 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);
|
||||
}
|
||||
}
|
48
src/main/java/com/sharebounds/sharebounds/camera/CameraGestures.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
100
src/main/java/com/sharebounds/sharebounds/camera/CameraTextureView.java
Executable 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();
|
||||
}
|
||||
}
|
72
src/main/java/com/sharebounds/sharebounds/camera/CameraUtils.java
Executable 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;
|
||||
}
|
||||
}
|
42
src/main/java/com/sharebounds/sharebounds/camera/CaptureButton.java
Executable 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);
|
||||
}
|
||||
}
|
63
src/main/java/com/sharebounds/sharebounds/camera/FlashButton.java
Executable 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);
|
||||
}
|
||||
}
|
60
src/main/java/com/sharebounds/sharebounds/camera/ImageData.java
Executable 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;
|
||||
}
|
||||
}
|
192
src/main/java/com/sharebounds/sharebounds/camera/ImageGestures.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
74
src/main/java/com/sharebounds/sharebounds/camera/ImageModel.java
Executable 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;
|
||||
}
|
||||
}
|
340
src/main/java/com/sharebounds/sharebounds/camera/OcrCamera.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
218
src/main/java/com/sharebounds/sharebounds/camera/OcrCameraController.java
Executable 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();
|
||||
}
|
||||
}
|
15
src/main/java/com/sharebounds/sharebounds/camera/OcrData.java
Executable 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);
|
||||
}
|
||||
}
|
72
src/main/java/com/sharebounds/sharebounds/camera/OcrDetector.java
Executable 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;
|
||||
}
|
||||
}
|
110
src/main/java/com/sharebounds/sharebounds/camera/OcrTask.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
247
src/main/java/com/sharebounds/sharebounds/keyboard/CameraIME.java
Executable 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;
|
||||
}
|
||||
}
|
104
src/main/java/com/sharebounds/sharebounds/keyboard/CameraIMEUIController.java
Executable 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
31
src/main/java/com/sharebounds/sharebounds/keyboard/FullScreenButton.java
Executable 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);
|
||||
}
|
||||
}
|
43
src/main/java/com/sharebounds/sharebounds/keyboard/KeyClickListener.java
Executable 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;
|
||||
}
|
||||
}
|
22
src/main/java/com/sharebounds/sharebounds/keyboard/KeyboardRepeatButton.java
Executable 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();
|
||||
}
|
||||
}
|
73
src/main/java/com/sharebounds/sharebounds/keyboard/KeyboardTextManager.java
Executable 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);
|
||||
}
|
||||
}
|
54
src/main/java/com/sharebounds/sharebounds/keyboard/RepeatListener.java
Executable 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;
|
||||
}
|
||||
}
|
18
src/main/java/com/sharebounds/sharebounds/keyboard/Utils.java
Executable 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);
|
||||
}
|
||||
}
|
40
src/main/java/com/sharebounds/sharebounds/main/BaseThemeActivity.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
23
src/main/java/com/sharebounds/sharebounds/main/BottomSheetFragment.java
Executable 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);
|
||||
}
|
||||
}
|
130
src/main/java/com/sharebounds/sharebounds/main/GalleryImageLoader.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
261
src/main/java/com/sharebounds/sharebounds/main/MainActivity.java
Executable 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
121
src/main/java/com/sharebounds/sharebounds/main/MainUIController.java
Executable 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();
|
||||
}
|
||||
}
|
49
src/main/java/com/sharebounds/sharebounds/main/PrivTouActivity.java
Executable 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();
|
||||
}
|
||||
}
|
32
src/main/java/com/sharebounds/sharebounds/main/SettingsActivity.java
Executable 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);
|
||||
}
|
||||
}
|
147
src/main/java/com/sharebounds/sharebounds/main/SettingsFragment.java
Executable 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);
|
||||
}
|
||||
}
|
96
src/main/java/com/sharebounds/sharebounds/main/ShareUtils.java
Executable 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();
|
||||
}
|
||||
}
|
140
src/main/java/com/sharebounds/sharebounds/main/TextActivity.java
Executable 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
@ -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>
|
BIN
src/main/res/drawable-hdpi/ic_backspace_black_24dp.png
Executable file
After Width: | Height: | Size: 328 B |
BIN
src/main/res/drawable-hdpi/ic_close_black_24dp.png
Executable file
After Width: | Height: | Size: 207 B |
BIN
src/main/res/drawable-hdpi/ic_flash_auto_black_24dp.png
Executable file
After Width: | Height: | Size: 370 B |
BIN
src/main/res/drawable-hdpi/ic_flash_off_black_24dp.png
Executable file
After Width: | Height: | Size: 285 B |
BIN
src/main/res/drawable-hdpi/ic_flash_on_black_24dp.png
Executable file
After Width: | Height: | Size: 199 B |
BIN
src/main/res/drawable-hdpi/ic_fullscreen_black_24dp.png
Executable file
After Width: | Height: | Size: 100 B |
BIN
src/main/res/drawable-hdpi/ic_fullscreen_exit_black_24dp.png
Executable file
After Width: | Height: | Size: 103 B |
BIN
src/main/res/drawable-hdpi/ic_keyboard_return_black_24dp.png
Executable file
After Width: | Height: | Size: 168 B |
BIN
src/main/res/drawable-hdpi/ic_language_black_24dp.png
Executable file
After Width: | Height: | Size: 589 B |
BIN
src/main/res/drawable-hdpi/ic_photo_library_black_24dp.png
Executable file
After Width: | Height: | Size: 245 B |
BIN
src/main/res/drawable-hdpi/ic_settings_black_24dp.png
Executable file
After Width: | Height: | Size: 453 B |
BIN
src/main/res/drawable-hdpi/ic_share_black_24dp.png
Executable file
After Width: | Height: | Size: 398 B |
BIN
src/main/res/drawable-hdpi/ic_space_bar_black_24dp.png
Executable file
After Width: | Height: | Size: 108 B |
BIN
src/main/res/drawable-hdpi/ic_text_fields_black_24dp.png
Executable file
After Width: | Height: | Size: 135 B |
BIN
src/main/res/drawable-mdpi/ic_backspace_black_24dp.png
Executable file
After Width: | Height: | Size: 231 B |
BIN
src/main/res/drawable-mdpi/ic_close_black_24dp.png
Executable file
After Width: | Height: | Size: 164 B |
BIN
src/main/res/drawable-mdpi/ic_flash_auto_black_24dp.png
Executable file
After Width: | Height: | Size: 256 B |
BIN
src/main/res/drawable-mdpi/ic_flash_off_black_24dp.png
Executable file
After Width: | Height: | Size: 208 B |
BIN
src/main/res/drawable-mdpi/ic_flash_on_black_24dp.png
Executable file
After Width: | Height: | Size: 155 B |
BIN
src/main/res/drawable-mdpi/ic_fullscreen_black_24dp.png
Executable file
After Width: | Height: | Size: 90 B |
BIN
src/main/res/drawable-mdpi/ic_fullscreen_exit_black_24dp.png
Executable file
After Width: | Height: | Size: 93 B |
BIN
src/main/res/drawable-mdpi/ic_keyboard_return_black_24dp.png
Executable file
After Width: | Height: | Size: 130 B |
BIN
src/main/res/drawable-mdpi/ic_language_black_24dp.png
Executable file
After Width: | Height: | Size: 375 B |
BIN
src/main/res/drawable-mdpi/ic_photo_library_black_24dp.png
Executable file
After Width: | Height: | Size: 188 B |
BIN
src/main/res/drawable-mdpi/ic_settings_black_24dp.png
Executable file
After Width: | Height: | Size: 322 B |
BIN
src/main/res/drawable-mdpi/ic_share_black_24dp.png
Executable file
After Width: | Height: | Size: 262 B |
BIN
src/main/res/drawable-mdpi/ic_space_bar_black_24dp.png
Executable file
After Width: | Height: | Size: 85 B |
BIN
src/main/res/drawable-mdpi/ic_text_fields_black_24dp.png
Executable file
After Width: | Height: | Size: 98 B |
12
src/main/res/drawable-v21/capture_button.xml
Executable 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>
|
12
src/main/res/drawable-v21/capture_button_dark.xml
Executable 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>
|
11
src/main/res/drawable-v21/round_button.xml
Executable 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>
|
11
src/main/res/drawable-v21/round_button_dark.xml
Executable 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>
|
BIN
src/main/res/drawable-xhdpi/ic_backspace_black_24dp.png
Executable file
After Width: | Height: | Size: 387 B |
BIN
src/main/res/drawable-xhdpi/ic_close_black_24dp.png
Executable file
After Width: | Height: | Size: 235 B |
BIN
src/main/res/drawable-xhdpi/ic_flash_auto_black_24dp.png
Executable file
After Width: | Height: | Size: 450 B |
BIN
src/main/res/drawable-xhdpi/ic_flash_off_black_24dp.png
Executable file
After Width: | Height: | Size: 311 B |
BIN
src/main/res/drawable-xhdpi/ic_flash_on_black_24dp.png
Executable file
After Width: | Height: | Size: 224 B |
BIN
src/main/res/drawable-xhdpi/ic_fullscreen_black_24dp.png
Executable file
After Width: | Height: | Size: 109 B |
BIN
src/main/res/drawable-xhdpi/ic_fullscreen_exit_black_24dp.png
Executable file
After Width: | Height: | Size: 111 B |
BIN
src/main/res/drawable-xhdpi/ic_keyboard_return_black_24dp.png
Executable file
After Width: | Height: | Size: 167 B |
BIN
src/main/res/drawable-xhdpi/ic_language_black_24dp.png
Executable file
After Width: | Height: | Size: 776 B |
BIN
src/main/res/drawable-xhdpi/ic_photo_library_black_24dp.png
Executable file
After Width: | Height: | Size: 304 B |
BIN
src/main/res/drawable-xhdpi/ic_settings_black_24dp.png
Executable file
After Width: | Height: | Size: 557 B |
BIN
src/main/res/drawable-xhdpi/ic_share_black_24dp.png
Executable file
After Width: | Height: | Size: 483 B |
BIN
src/main/res/drawable-xhdpi/ic_space_bar_black_24dp.png
Executable file
After Width: | Height: | Size: 100 B |
BIN
src/main/res/drawable-xhdpi/ic_text_fields_black_24dp.png
Executable file
After Width: | Height: | Size: 110 B |
BIN
src/main/res/drawable-xxhdpi/ic_backspace_black_24dp.png
Executable file
After Width: | Height: | Size: 534 B |
BIN
src/main/res/drawable-xxhdpi/ic_close_black_24dp.png
Executable file
After Width: | Height: | Size: 309 B |
BIN
src/main/res/drawable-xxhdpi/ic_flash_auto_black_24dp.png
Executable file
After Width: | Height: | Size: 638 B |
BIN
src/main/res/drawable-xxhdpi/ic_flash_off_black_24dp.png
Executable file
After Width: | Height: | Size: 435 B |
BIN
src/main/res/drawable-xxhdpi/ic_flash_on_black_24dp.png
Executable file
After Width: | Height: | Size: 294 B |