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 |