Floating calculator refactor + dedicated preferences file for it
This commit is contained in:
parent
8c897ba441
commit
95b22cd253
@ -1,6 +1,7 @@
|
|||||||
package org.solovyev.android.calculator;
|
package org.solovyev.android.calculator;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -8,18 +9,23 @@ import android.os.Looper;
|
|||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.squareup.otto.Bus;
|
import com.squareup.otto.Bus;
|
||||||
import com.squareup.otto.GeneratedHandlerFinder;
|
import com.squareup.otto.GeneratedHandlerFinder;
|
||||||
|
|
||||||
|
import org.solovyev.android.UiThreadExecutor;
|
||||||
|
import org.solovyev.android.checkout.Billing;
|
||||||
|
import org.solovyev.android.checkout.Checkout;
|
||||||
|
import org.solovyev.android.checkout.Inventory;
|
||||||
|
import org.solovyev.android.checkout.ProductTypes;
|
||||||
|
import org.solovyev.android.checkout.Products;
|
||||||
|
import org.solovyev.android.checkout.RobotmediaDatabase;
|
||||||
|
import org.solovyev.android.checkout.RobotmediaInventory;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import jscl.JsclMathEngine;
|
import jscl.JsclMathEngine;
|
||||||
import org.solovyev.android.UiThreadExecutor;
|
|
||||||
import org.solovyev.android.checkout.*;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@ -27,6 +33,11 @@ import java.util.concurrent.Executors;
|
|||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class AppModule {
|
public class AppModule {
|
||||||
|
|
||||||
@ -37,6 +48,7 @@ public class AppModule {
|
|||||||
// multiple threads
|
// multiple threads
|
||||||
public static final String THREAD_BACKGROUND = "thread-background";
|
public static final String THREAD_BACKGROUND = "thread-background";
|
||||||
public static final String DIR_FILES = "dir-files";
|
public static final String DIR_FILES = "dir-files";
|
||||||
|
public static final String PREFS_FLOATING = "prefs-floating";
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Application application;
|
private final Application application;
|
||||||
@ -69,6 +81,13 @@ public class AppModule {
|
|||||||
return PreferenceManager.getDefaultSharedPreferences(application);
|
return PreferenceManager.getDefaultSharedPreferences(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named(PREFS_FLOATING)
|
||||||
|
SharedPreferences provideFloatingPreferences() {
|
||||||
|
return application.getSharedPreferences("floating-calculator", Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Named(THREAD_INIT)
|
@Named(THREAD_INIT)
|
||||||
|
@ -94,25 +94,18 @@ public class FloatingCalculatorService extends Service implements FloatingViewLi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final WindowManager wm = ((WindowManager) this.getSystemService(Context.WINDOW_SERVICE));
|
final WindowManager wm = ((WindowManager) this.getSystemService(Context.WINDOW_SERVICE));
|
||||||
|
|
||||||
final DisplayMetrics dm = getResources().getDisplayMetrics();
|
final DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||||
|
final android.view.Display dd = wm.getDefaultDisplay();
|
||||||
|
|
||||||
int twoThirdWidth = 2 * wm.getDefaultDisplay().getWidth() / 3;
|
//noinspection deprecation
|
||||||
int twoThirdHeight = 2 * wm.getDefaultDisplay().getHeight() / 3;
|
final int maxWidth = 2 * Math.min(dd.getWidth(), dd.getHeight()) / 3;
|
||||||
|
final int desiredWidth = Views.toPixels(dm, 300);
|
||||||
|
|
||||||
twoThirdWidth = Math.min(twoThirdWidth, twoThirdHeight);
|
final int width = Math.min(maxWidth, desiredWidth);
|
||||||
twoThirdHeight = Math.max(twoThirdWidth, getHeight(twoThirdWidth));
|
final int height = getHeight(width);
|
||||||
|
|
||||||
final int baseWidth = Views.toPixels(dm, 300);
|
final FloatingCalculatorView.State state = new FloatingCalculatorView.State(width, height, -1, -1);
|
||||||
final int width0 = Math.min(twoThirdWidth, baseWidth);
|
view = new FloatingCalculatorView(this, state, this);
|
||||||
final int height0 = Math.min(twoThirdHeight, getHeight(baseWidth));
|
|
||||||
|
|
||||||
final int width = Math.min(width0, height0);
|
|
||||||
final int height = Math.max(width0, height0);
|
|
||||||
|
|
||||||
view = FloatingCalculatorView
|
|
||||||
.create(this, FloatingCalculatorViewState.create(width, height, -1, -1), this,
|
|
||||||
preferences);
|
|
||||||
view.show();
|
view.show();
|
||||||
view.updateEditorState(editor.getState());
|
view.updateEditorState(editor.getState());
|
||||||
view.updateDisplayState(display.getState());
|
view.updateDisplayState(display.getState());
|
||||||
|
@ -33,16 +33,18 @@ import android.content.SharedPreferences;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.preference.PreferenceManager;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.view.Display;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import org.solovyev.android.calculator.AppModule;
|
||||||
import org.solovyev.android.calculator.DisplayState;
|
import org.solovyev.android.calculator.DisplayState;
|
||||||
import org.solovyev.android.calculator.DisplayView;
|
import org.solovyev.android.calculator.DisplayView;
|
||||||
import org.solovyev.android.calculator.Editor;
|
import org.solovyev.android.calculator.Editor;
|
||||||
@ -52,20 +54,199 @@ import org.solovyev.android.calculator.Keyboard;
|
|||||||
import org.solovyev.android.calculator.Preferences;
|
import org.solovyev.android.calculator.Preferences;
|
||||||
import org.solovyev.android.calculator.R;
|
import org.solovyev.android.calculator.R;
|
||||||
import org.solovyev.android.calculator.buttons.CppButton;
|
import org.solovyev.android.calculator.buttons.CppButton;
|
||||||
import org.solovyev.android.prefs.Preference;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
public class FloatingCalculatorView {
|
public class FloatingCalculatorView {
|
||||||
private static final String TAG = FloatingCalculatorView.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final Preference<FloatingCalculatorViewState> viewStatePreference = new FloatingCalculatorViewState.Preference("onscreen_view_state", FloatingCalculatorViewState
|
private static class MyTouchListener implements View.OnTouchListener {
|
||||||
.createDefault());
|
private static final float DIST_EPS = 0f;
|
||||||
|
private static final float DIST_MAX = 100000f;
|
||||||
|
private static final long TIME_EPS = 0L;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private final WindowManager wm;
|
||||||
|
@Nonnull
|
||||||
|
private final View view;
|
||||||
|
private int orientation;
|
||||||
|
private float x0;
|
||||||
|
private float y0;
|
||||||
|
private long lastMoveTime = 0;
|
||||||
|
private final DisplayMetrics dm = new DisplayMetrics();
|
||||||
|
|
||||||
|
public MyTouchListener(@Nonnull WindowManager wm,
|
||||||
|
@Nonnull View view) {
|
||||||
|
this.wm = wm;
|
||||||
|
this.view = view;
|
||||||
|
onDisplayChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDisplayChanged() {
|
||||||
|
final Display dd = wm.getDefaultDisplay();
|
||||||
|
//noinspection deprecation
|
||||||
|
orientation = dd.getOrientation();
|
||||||
|
dd.getMetrics(dm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
//noinspection deprecation
|
||||||
|
if (orientation != wm.getDefaultDisplay().getOrientation()) {
|
||||||
|
// orientation has changed => we need to check display width/height each time window moved
|
||||||
|
onDisplayChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
final float x1 = event.getRawX();
|
||||||
|
final float y1 = event.getRawY();
|
||||||
|
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
x0 = x1;
|
||||||
|
y0 = y1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
if (now - lastMoveTime >= TIME_EPS) {
|
||||||
|
lastMoveTime = now;
|
||||||
|
processMove(x1, y1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processMove(float x1, float y1) {
|
||||||
|
final float Δx = x1 - x0;
|
||||||
|
final float Δy = y1 - y0;
|
||||||
|
|
||||||
|
final WindowManager.LayoutParams params =
|
||||||
|
(WindowManager.LayoutParams) view.getLayoutParams();
|
||||||
|
|
||||||
|
boolean xInBounds = isDistanceInBounds(Δx);
|
||||||
|
boolean yInBounds = isDistanceInBounds(Δy);
|
||||||
|
if (xInBounds || yInBounds) {
|
||||||
|
|
||||||
|
if (xInBounds) {
|
||||||
|
params.x = (int) (params.x + Δx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yInBounds) {
|
||||||
|
params.y = (int) (params.y + Δy);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.x = Math.min(Math.max(params.x, 0), dm.widthPixels - params.width);
|
||||||
|
params.y = Math.min(Math.max(params.y, 0), dm.heightPixels - params.height);
|
||||||
|
|
||||||
|
wm.updateViewLayout(view, params);
|
||||||
|
|
||||||
|
if (xInBounds) {
|
||||||
|
x0 = x1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yInBounds) {
|
||||||
|
y0 = y1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDistanceInBounds(float δx) {
|
||||||
|
δx = Math.abs(δx);
|
||||||
|
return δx >= DIST_EPS && δx < DIST_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class State implements Parcelable {
|
||||||
|
|
||||||
|
public static final Creator<State> CREATOR = new Creator<State>() {
|
||||||
|
public State createFromParcel(@Nonnull Parcel in) {
|
||||||
|
return new State(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public State[] newArray(int size) {
|
||||||
|
return new State[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public final int width;
|
||||||
|
public final int height;
|
||||||
|
public final int x;
|
||||||
|
public final int y;
|
||||||
|
|
||||||
|
public State(int width, int height, int x, int y) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
private State(@NonNull SharedPreferences prefs) {
|
||||||
|
width = prefs.getInt("width", 200);
|
||||||
|
height = prefs.getInt("height", 400);
|
||||||
|
x = prefs.getInt("x", 0);
|
||||||
|
y = prefs.getInt("y", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public State(@Nonnull Parcel in) {
|
||||||
|
width = in.readInt();
|
||||||
|
height = in.readInt();
|
||||||
|
x = in.readInt();
|
||||||
|
y = in.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@android.support.annotation.Nullable
|
||||||
|
public static State fromPrefs(@NonNull SharedPreferences prefs) {
|
||||||
|
if(!prefs.contains("width")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new State(prefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@Nonnull Parcel out, int flags) {
|
||||||
|
out.writeInt(width);
|
||||||
|
out.writeInt(height);
|
||||||
|
out.writeInt(x);
|
||||||
|
out.writeInt(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "State{" +
|
||||||
|
"y=" + y +
|
||||||
|
", x=" + x +
|
||||||
|
", height=" + height +
|
||||||
|
", width=" + width +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(@NonNull SharedPreferences.Editor editor) {
|
||||||
|
editor.putInt("width", width);
|
||||||
|
editor.putInt("height", height);
|
||||||
|
editor.putInt("x", x);
|
||||||
|
editor.putInt("y", y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@NonNull
|
||||||
|
private final Context context;
|
||||||
|
@NonNull
|
||||||
|
private final FloatingViewListener listener;
|
||||||
|
@Inject
|
||||||
|
Keyboard keyboard;
|
||||||
|
@Inject
|
||||||
|
Editor editor;
|
||||||
|
@Inject
|
||||||
|
SharedPreferences preferences;
|
||||||
|
@Named(AppModule.PREFS_FLOATING)
|
||||||
|
@Inject
|
||||||
|
SharedPreferences myPreferences;
|
||||||
private View root;
|
private View root;
|
||||||
private View content;
|
private View content;
|
||||||
private View header;
|
private View header;
|
||||||
@ -73,61 +254,30 @@ public class FloatingCalculatorView {
|
|||||||
private Drawable headerTitleDrawable;
|
private Drawable headerTitleDrawable;
|
||||||
private EditorView editorView;
|
private EditorView editorView;
|
||||||
private DisplayView displayView;
|
private DisplayView displayView;
|
||||||
private Context context;
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private FloatingCalculatorViewState state = FloatingCalculatorViewState.createDefault();
|
private final State state;
|
||||||
@Nullable
|
|
||||||
private FloatingViewListener viewListener;
|
|
||||||
@Inject
|
|
||||||
Keyboard keyboard;
|
|
||||||
@Inject
|
|
||||||
Editor editor;
|
|
||||||
|
|
||||||
private boolean minimized;
|
private boolean minimized;
|
||||||
private boolean attached;
|
private boolean attached;
|
||||||
private boolean folded;
|
private boolean folded;
|
||||||
private boolean initialized;
|
private boolean initialized;
|
||||||
private boolean shown;
|
private boolean shown;
|
||||||
|
|
||||||
|
public FloatingCalculatorView(@Nonnull Context context,
|
||||||
private FloatingCalculatorView() {
|
@Nonnull State state,
|
||||||
}
|
@NonNull FloatingViewListener listener) {
|
||||||
|
cast(context).getComponent().inject(this);
|
||||||
public static FloatingCalculatorView create(@Nonnull Context context,
|
this.context = context;
|
||||||
@Nonnull FloatingCalculatorViewState state,
|
this.listener = listener;
|
||||||
@Nullable FloatingViewListener viewListener,
|
final Preferences.SimpleTheme theme =
|
||||||
@NonNull SharedPreferences preferences) {
|
Preferences.Onscreen.theme.getPreferenceNoError(preferences);
|
||||||
final FloatingCalculatorView view = new FloatingCalculatorView();
|
final Preferences.Gui.Theme appTheme =
|
||||||
cast(context).getComponent().inject(view);
|
Preferences.Gui.theme.getPreferenceNoError(preferences);
|
||||||
final Preferences.SimpleTheme theme = Preferences.Onscreen.theme.getPreferenceNoError(preferences);
|
this.root = View.inflate(context, theme.getOnscreenLayout(appTheme), null);
|
||||||
final Preferences.Gui.Theme appTheme = Preferences.Gui.theme.getPreferenceNoError(preferences);
|
final State persistedState = State.fromPrefs(myPreferences);
|
||||||
view.root = View.inflate(context, theme.getOnscreenLayout(appTheme), null);
|
|
||||||
view.context = context;
|
|
||||||
view.viewListener = viewListener;
|
|
||||||
|
|
||||||
final FloatingCalculatorViewState persistedState = readState(context);
|
|
||||||
if (persistedState != null) {
|
if (persistedState != null) {
|
||||||
view.state = persistedState;
|
this.state = persistedState;
|
||||||
} else {
|
} else {
|
||||||
view.state = state;
|
this.state = state;
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void persistState(@Nonnull Context context, @Nonnull
|
|
||||||
FloatingCalculatorViewState state) {
|
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
viewStatePreference.putPreference(preferences, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static FloatingCalculatorViewState readState(@Nonnull Context context) {
|
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
if (viewStatePreference.isSet(preferences)) {
|
|
||||||
return viewStatePreference.getPreference(preferences);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +286,12 @@ public class FloatingCalculatorView {
|
|||||||
displayView.setState(displayState);
|
displayView.setState(displayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkInit() {
|
||||||
|
if (!initialized) {
|
||||||
|
throw new IllegalStateException("init() must be called!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void updateEditorState(@Nonnull EditorState editorState) {
|
public void updateEditorState(@Nonnull EditorState editorState) {
|
||||||
checkInit();
|
checkInit();
|
||||||
editorView.setState(editorState);
|
editorView.setState(editorState);
|
||||||
@ -144,7 +300,8 @@ public class FloatingCalculatorView {
|
|||||||
private void setHeight(int height) {
|
private void setHeight(int height) {
|
||||||
checkInit();
|
checkInit();
|
||||||
|
|
||||||
final WindowManager.LayoutParams params = (WindowManager.LayoutParams) root.getLayoutParams();
|
final WindowManager.LayoutParams params =
|
||||||
|
(WindowManager.LayoutParams) root.getLayoutParams();
|
||||||
params.height = height;
|
params.height = height;
|
||||||
getWindowManager().updateViewLayout(root, params);
|
getWindowManager().updateViewLayout(root, params);
|
||||||
}
|
}
|
||||||
@ -164,7 +321,8 @@ public class FloatingCalculatorView {
|
|||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (keyboard.buttonPressed(widgetButton.action)) {
|
if (keyboard.buttonPressed(widgetButton.action)) {
|
||||||
if (keyboard.isVibrateOnKeypress()) {
|
if (keyboard.isVibrateOnKeypress()) {
|
||||||
v.performHapticFeedback(KEYBOARD_TAP, FLAG_IGNORE_GLOBAL_SETTING | FLAG_IGNORE_VIEW_SETTING);
|
v.performHapticFeedback(KEYBOARD_TAP,
|
||||||
|
FLAG_IGNORE_GLOBAL_SETTING | FLAG_IGNORE_VIEW_SETTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (widgetButton == CppButton.app) {
|
if (widgetButton == CppButton.app) {
|
||||||
@ -177,7 +335,8 @@ public class FloatingCalculatorView {
|
|||||||
public boolean onLongClick(View v) {
|
public boolean onLongClick(View v) {
|
||||||
if (keyboard.buttonPressed(widgetButton.actionLong)) {
|
if (keyboard.buttonPressed(widgetButton.actionLong)) {
|
||||||
if (keyboard.isVibrateOnKeypress()) {
|
if (keyboard.isVibrateOnKeypress()) {
|
||||||
v.performHapticFeedback(LONG_PRESS, FLAG_IGNORE_GLOBAL_SETTING | FLAG_IGNORE_VIEW_SETTING);
|
v.performHapticFeedback(LONG_PRESS,
|
||||||
|
FLAG_IGNORE_GLOBAL_SETTING | FLAG_IGNORE_VIEW_SETTING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -218,25 +377,20 @@ public class FloatingCalculatorView {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
root.findViewById(R.id.onscreen_close_button).setOnClickListener(new View.OnClickListener() {
|
root.findViewById(R.id.onscreen_close_button)
|
||||||
@Override
|
.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View v) {
|
@Override
|
||||||
hide();
|
public void onClick(View v) {
|
||||||
}
|
hide();
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
headerTitle.setOnTouchListener(new WindowDragTouchListener(wm, root));
|
headerTitle.setOnTouchListener(new MyTouchListener(wm, root));
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkInit() {
|
|
||||||
if (!initialized) {
|
|
||||||
throw new IllegalStateException("init() must be called!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show() {
|
public void show() {
|
||||||
if (shown) {
|
if (shown) {
|
||||||
return;
|
return;
|
||||||
@ -253,12 +407,14 @@ public class FloatingCalculatorView {
|
|||||||
final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||||
if (!attached) {
|
if (!attached) {
|
||||||
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
|
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
|
||||||
state.getWidth(),
|
state.width,
|
||||||
state.getHeight(),
|
state.height,
|
||||||
state.getX(),
|
state.x,
|
||||||
state.getY(),
|
state.y,
|
||||||
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
|
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
|
||||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||||
|
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||||
|
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
|
||||||
PixelFormat.TRANSLUCENT);
|
PixelFormat.TRANSLUCENT);
|
||||||
|
|
||||||
params.gravity = Gravity.TOP | Gravity.LEFT;
|
params.gravity = Gravity.TOP | Gravity.LEFT;
|
||||||
@ -272,7 +428,8 @@ public class FloatingCalculatorView {
|
|||||||
if (!folded) {
|
if (!folded) {
|
||||||
headerTitle.setImageDrawable(headerTitleDrawable);
|
headerTitle.setImageDrawable(headerTitleDrawable);
|
||||||
final Resources r = header.getResources();
|
final Resources r = header.getResources();
|
||||||
final int newHeight = header.getHeight() + 2 * r.getDimensionPixelSize(R.dimen.cpp_onscreen_main_padding);
|
final int newHeight = header.getHeight() + 2 * r
|
||||||
|
.getDimensionPixelSize(R.dimen.cpp_onscreen_main_padding);
|
||||||
content.setVisibility(View.GONE);
|
content.setVisibility(View.GONE);
|
||||||
setHeight(newHeight);
|
setHeight(newHeight);
|
||||||
folded = true;
|
folded = true;
|
||||||
@ -283,7 +440,7 @@ public class FloatingCalculatorView {
|
|||||||
if (folded) {
|
if (folded) {
|
||||||
headerTitle.setImageDrawable(null);
|
headerTitle.setImageDrawable(null);
|
||||||
content.setVisibility(View.VISIBLE);
|
content.setVisibility(View.VISIBLE);
|
||||||
setHeight(state.getHeight());
|
setHeight(state.height);
|
||||||
folded = false;
|
folded = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,14 +457,9 @@ public class FloatingCalculatorView {
|
|||||||
public void minimize() {
|
public void minimize() {
|
||||||
checkInit();
|
checkInit();
|
||||||
if (!minimized) {
|
if (!minimized) {
|
||||||
persistState(context, getCurrentState(!folded));
|
saveState();
|
||||||
|
|
||||||
detach();
|
detach();
|
||||||
|
listener.onViewMinimized();
|
||||||
if (viewListener != null) {
|
|
||||||
viewListener.onViewMinimized();
|
|
||||||
}
|
|
||||||
|
|
||||||
minimized = true;
|
minimized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,150 +469,31 @@ public class FloatingCalculatorView {
|
|||||||
if (!shown) {
|
if (!shown) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
saveState();
|
||||||
persistState(context, getCurrentState(!folded));
|
|
||||||
|
|
||||||
detach();
|
detach();
|
||||||
|
listener.onViewHidden();
|
||||||
if (viewListener != null) {
|
|
||||||
viewListener.onViewHidden();
|
|
||||||
}
|
|
||||||
|
|
||||||
shown = false;
|
shown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveState() {
|
||||||
|
final SharedPreferences.Editor editor = myPreferences.edit();
|
||||||
|
getState().save(editor);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private WindowManager getWindowManager() {
|
private WindowManager getWindowManager() {
|
||||||
return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE));
|
return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public FloatingCalculatorViewState getCurrentState(boolean useRealSize) {
|
public State getState() {
|
||||||
final WindowManager.LayoutParams params = (WindowManager.LayoutParams) root.getLayoutParams();
|
final WindowManager.LayoutParams params =
|
||||||
if (useRealSize) {
|
(WindowManager.LayoutParams) root.getLayoutParams();
|
||||||
return FloatingCalculatorViewState
|
if (!folded) {
|
||||||
.create(params.width, params.height, params.x, params.y);
|
return new State(params.width, params.height, params.x, params.y);
|
||||||
} else {
|
} else {
|
||||||
return FloatingCalculatorViewState
|
return new State(state.width, state.height, params.x, params.y);
|
||||||
.create(state.getWidth(), state.getHeight(), params.x, params.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class WindowDragTouchListener implements View.OnTouchListener {
|
|
||||||
private static final float DIST_EPS = 0f;
|
|
||||||
private static final float DIST_MAX = 100000f;
|
|
||||||
private static final long TIME_EPS = 0L;
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private final WindowManager wm;
|
|
||||||
@Nonnull
|
|
||||||
private final View view;
|
|
||||||
private int orientation;
|
|
||||||
private float x0;
|
|
||||||
private float y0;
|
|
||||||
private long time = 0;
|
|
||||||
private int displayWidth;
|
|
||||||
private int displayHeight;
|
|
||||||
|
|
||||||
public WindowDragTouchListener(@Nonnull WindowManager wm,
|
|
||||||
@Nonnull View view) {
|
|
||||||
this.wm = wm;
|
|
||||||
this.view = view;
|
|
||||||
initDisplayParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private static String toString(float x, float y) {
|
|
||||||
return "(" + formatFloat(x) + ", " + formatFloat(y) + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String formatFloat(float value) {
|
|
||||||
if (value >= 0) {
|
|
||||||
return "+" + String.format("%.2f", value);
|
|
||||||
} else {
|
|
||||||
return String.format(Locale.ENGLISH, "%.2f", value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
|
||||||
if (orientation != this.wm.getDefaultDisplay().getOrientation()) {
|
|
||||||
// orientation has changed => we need to check display width/height each time window moved
|
|
||||||
initDisplayParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Log.d(TAG, "Action: " + event.getAction());
|
|
||||||
|
|
||||||
final float x1 = event.getRawX();
|
|
||||||
final float y1 = event.getRawY();
|
|
||||||
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
Log.d(TAG, "0:" + toString(x0, y0) + ", 1: " + toString(x1, y1));
|
|
||||||
x0 = x1;
|
|
||||||
y0 = y1;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
final long currentTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
if (currentTime - time >= TIME_EPS) {
|
|
||||||
time = currentTime;
|
|
||||||
processMove(x1, y1);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDisplayParams() {
|
|
||||||
this.orientation = this.wm.getDefaultDisplay().getOrientation();
|
|
||||||
|
|
||||||
final DisplayMetrics displayMetrics = new DisplayMetrics();
|
|
||||||
wm.getDefaultDisplay().getMetrics(displayMetrics);
|
|
||||||
|
|
||||||
this.displayWidth = displayMetrics.widthPixels;
|
|
||||||
this.displayHeight = displayMetrics.heightPixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processMove(float x1, float y1) {
|
|
||||||
final float Δx = x1 - x0;
|
|
||||||
final float Δy = y1 - y0;
|
|
||||||
|
|
||||||
final WindowManager.LayoutParams params = (WindowManager.LayoutParams) view.getLayoutParams();
|
|
||||||
Log.d(TAG, "0:" + toString(x0, y0) + ", 1: " + toString(x1, y1) + ", Δ: " + toString(Δx, Δy) + ", params: " + toString(params.x, params.y));
|
|
||||||
|
|
||||||
boolean xInBounds = isDistanceInBounds(Δx);
|
|
||||||
boolean yInBounds = isDistanceInBounds(Δy);
|
|
||||||
if (xInBounds || yInBounds) {
|
|
||||||
|
|
||||||
if (xInBounds) {
|
|
||||||
params.x = (int) (params.x + Δx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (yInBounds) {
|
|
||||||
params.y = (int) (params.y + Δy);
|
|
||||||
}
|
|
||||||
|
|
||||||
params.x = Math.min(Math.max(params.x, 0), displayWidth - params.width);
|
|
||||||
params.y = Math.min(Math.max(params.y, 0), displayHeight - params.height);
|
|
||||||
|
|
||||||
wm.updateViewLayout(view, params);
|
|
||||||
|
|
||||||
if (xInBounds) {
|
|
||||||
x0 = x1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (yInBounds) {
|
|
||||||
y0 = y1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDistanceInBounds(float δx) {
|
|
||||||
δx = Math.abs(δx);
|
|
||||||
return δx >= DIST_EPS && δx < DIST_MAX;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 serso aka se.solovyev
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
* Contact details
|
|
||||||
*
|
|
||||||
* Email: se.solovyev@gmail.com
|
|
||||||
* Site: http://se.solovyev.org
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.solovyev.android.calculator.floating;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.solovyev.android.prefs.AbstractPreference;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public class FloatingCalculatorViewState implements Parcelable {
|
|
||||||
|
|
||||||
private static final String TAG = FloatingCalculatorViewState.class.getSimpleName();
|
|
||||||
private int width;
|
|
||||||
private int height;
|
|
||||||
private int x;
|
|
||||||
private int y;
|
|
||||||
public static final Parcelable.Creator<FloatingCalculatorViewState> CREATOR = new Parcelable.Creator<FloatingCalculatorViewState>() {
|
|
||||||
public FloatingCalculatorViewState createFromParcel(@Nonnull Parcel in) {
|
|
||||||
return FloatingCalculatorViewState.fromParcel(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FloatingCalculatorViewState[] newArray(int size) {
|
|
||||||
return new FloatingCalculatorViewState[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private FloatingCalculatorViewState() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private static FloatingCalculatorViewState fromParcel(@Nonnull Parcel in) {
|
|
||||||
final FloatingCalculatorViewState result = new FloatingCalculatorViewState();
|
|
||||||
result.width = in.readInt();
|
|
||||||
result.height = in.readInt();
|
|
||||||
result.x = in.readInt();
|
|
||||||
result.y = in.readInt();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public static FloatingCalculatorViewState createDefault() {
|
|
||||||
return create(200, 400, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public static FloatingCalculatorViewState create(int width, int height, int x, int y) {
|
|
||||||
final FloatingCalculatorViewState result = new FloatingCalculatorViewState();
|
|
||||||
result.width = width;
|
|
||||||
result.height = height;
|
|
||||||
result.x = x;
|
|
||||||
result.y = y;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWidth() {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWidth(int width) {
|
|
||||||
this.width = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHeight() {
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeight(int height) {
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getX() {
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setX(int x) {
|
|
||||||
this.x = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getY() {
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setY(int y) {
|
|
||||||
this.y = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(@Nonnull Parcel out, int flags) {
|
|
||||||
out.writeInt(width);
|
|
||||||
out.writeInt(height);
|
|
||||||
out.writeInt(x);
|
|
||||||
out.writeInt(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "CalculatorOnscreenViewState{" +
|
|
||||||
"y=" + y +
|
|
||||||
", x=" + x +
|
|
||||||
", height=" + height +
|
|
||||||
", width=" + width +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Preference extends AbstractPreference<FloatingCalculatorViewState> {
|
|
||||||
|
|
||||||
public Preference(@Nonnull String key, @Nullable FloatingCalculatorViewState defaultValue) {
|
|
||||||
super(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
protected FloatingCalculatorViewState getPersistedValue(@Nonnull SharedPreferences preferences) {
|
|
||||||
try {
|
|
||||||
final FloatingCalculatorViewState result = new FloatingCalculatorViewState();
|
|
||||||
final JSONObject jsonObject = new JSONObject(preferences.getString(getKey(), "{}"));
|
|
||||||
result.width = jsonObject.getInt("width");
|
|
||||||
result.height = jsonObject.getInt("height");
|
|
||||||
result.x = jsonObject.getInt("x");
|
|
||||||
result.y = jsonObject.getInt("y");
|
|
||||||
|
|
||||||
Log.d(TAG, "Reading onscreen view state: " + result);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return getDefaultValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void putPersistedValue(@Nonnull SharedPreferences.Editor editor, @Nonnull
|
|
||||||
FloatingCalculatorViewState value) {
|
|
||||||
final Map<String, Object> properties = new HashMap<String, Object>();
|
|
||||||
properties.put("width", value.getWidth());
|
|
||||||
properties.put("height", value.getHeight());
|
|
||||||
properties.put("x", value.getX());
|
|
||||||
properties.put("y", value.getY());
|
|
||||||
|
|
||||||
final JSONObject jsonObject = new JSONObject(properties);
|
|
||||||
|
|
||||||
final String json = jsonObject.toString();
|
|
||||||
Log.d(TAG, "Persisting onscreen view state: " + json);
|
|
||||||
editor.putString(getKey(), json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user