Fix losing widget state on restart

This commit is contained in:
serso 2016-03-01 14:01:59 +01:00
parent 4a8c0b7a2d
commit ea21bbe811
4 changed files with 101 additions and 14 deletions

View File

@ -26,7 +26,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.ContextMenu; import android.view.ContextMenu;
import jscl.math.Generic;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.solovyev.android.calculator.jscl.JsclOperation; import org.solovyev.android.calculator.jscl.JsclOperation;
@ -34,6 +34,8 @@ import org.solovyev.android.calculator.jscl.JsclOperation;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import jscl.math.Generic;
public class DisplayState implements Parcelable, ContextMenu.ContextMenuInfo { public class DisplayState implements Parcelable, ContextMenu.ContextMenuInfo {
public static final Creator<DisplayState> CREATOR = new Creator<DisplayState>() { public static final Creator<DisplayState> CREATOR = new Creator<DisplayState>() {
@ -143,4 +145,8 @@ public class DisplayState implements Parcelable, ContextMenu.ContextMenuInfo {
dest.writeString(text); dest.writeString(text);
dest.writeByte((byte) (valid ? 1 : 0)); dest.writeByte((byte) (valid ? 1 : 0));
} }
public boolean isEmpty() {
return valid && TextUtils.isEmpty(text);
}
} }

View File

@ -4,8 +4,11 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Vibrator; import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import org.solovyev.android.calculator.buttons.CppButton; import org.solovyev.android.calculator.buttons.CppButton;
import org.solovyev.android.calculator.history.History;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.inject.Inject; import javax.inject.Inject;
@ -19,6 +22,8 @@ public final class WidgetReceiver extends BroadcastReceiver {
@Inject @Inject
Keyboard keyboard; Keyboard keyboard;
@Inject
History history;
@Nonnull @Nonnull
public static Intent newButtonClickedIntent(@Nonnull Context context, @Nonnull CppButton button) { public static Intent newButtonClickedIntent(@Nonnull Context context, @Nonnull CppButton button) {
@ -43,9 +48,21 @@ public final class WidgetReceiver extends BroadcastReceiver {
return; return;
} }
if (history.isLoaded()) {
if (!keyboard.buttonPressed(button.action)) { if (!keyboard.buttonPressed(button.action)) {
// prevent vibrate
return; return;
} }
} else {
// if app has been killed we need first to restore the state and only after doing this
// to apply actions. Otherwise, we will apply actions on the empty editor
history.runWhenLoaded(new MyRunnable(keyboard, button.action));
}
vibrate(context);
}
private void vibrate(@NonNull Context context) {
if (!keyboard.isVibrateOnKeypress()) { if (!keyboard.isVibrateOnKeypress()) {
return; return;
} }
@ -55,4 +72,21 @@ public final class WidgetReceiver extends BroadcastReceiver {
} }
vibrator.vibrate(10); vibrator.vibrate(10);
} }
private static class MyRunnable implements Runnable {
@NonNull
private final Keyboard keyboard;
@NonNull
private final String action;
public MyRunnable(@NonNull Keyboard keyboard, @NonNull String action) {
this.keyboard = keyboard;
this.action = action;
}
@Override
public void run() {
keyboard.buttonPressed(action);
}
}
} }

View File

@ -77,6 +77,9 @@ public class History {
private final RecentHistory recent = new RecentHistory(); private final RecentHistory recent = new RecentHistory();
@Nonnull @Nonnull
private final List<HistoryState> saved = new ArrayList<>(); private final List<HistoryState> saved = new ArrayList<>();
@Nonnull
private final List<Runnable> whenLoadedRunnables = new ArrayList<>();
private boolean loaded;
@Inject @Inject
Application application; Application application;
@Inject @Inject
@ -217,15 +220,32 @@ public class History {
handler.post(new Runnable() { handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
Check.isTrue(recent.isEmpty()); onLoaded(recentStates, savedStates);
Check.isTrue(saved.isEmpty());
recent.addInitial(recentStates);
saved.addAll(savedStates);
editor.onHistoryLoaded(recent);
} }
}); });
} }
private void onLoaded(@NonNull List<HistoryState> recentStates, @NonNull List<HistoryState> savedStates) {
Check.isTrue(saved.isEmpty());
Check.isMainThread();
final boolean wasEmpty = recent.isEmpty();
recent.addInitial(recentStates);
saved.addAll(savedStates);
if (wasEmpty) {
// user has typed nothing while we were loading, let's use recent history to restore
// editor state
editor.onHistoryLoaded(recent);
} else {
// user has types something => we should schedule save
postRecentWrite();
}
loaded = true;
for (Runnable runnable : whenLoadedRunnables) {
runnable.run();
}
whenLoadedRunnables.clear();
}
@Nonnull @Nonnull
private List<HistoryState> tryLoadStates(@NonNull File file) { private List<HistoryState> tryLoadStates(@NonNull File file) {
try { try {
@ -238,6 +258,10 @@ public class History {
public void addRecent(@Nonnull HistoryState state) { public void addRecent(@Nonnull HistoryState state) {
Check.isMainThread(); Check.isMainThread();
if (recent.isEmpty() && state.isEmpty()) {
// don't add empty states to empty history
return;
}
recent.add(state); recent.add(state);
onRecentChanged(new AddedEvent(state, true)); onRecentChanged(new AddedEvent(state, true));
} }
@ -255,15 +279,23 @@ public class History {
} }
private void onRecentChanged(@Nonnull Object event) { private void onRecentChanged(@Nonnull Object event) {
handler.removeCallbacks(writeRecent); postRecentWrite();
handler.postDelayed(writeRecent, 5000);
bus.post(event); bus.post(event);
} }
private void postRecentWrite() {
handler.removeCallbacks(writeRecent);
handler.postDelayed(writeRecent, 5000);
}
private void onSavedChanged(@Nonnull Object event) { private void onSavedChanged(@Nonnull Object event) {
postSavedWrite();
bus.post(event);
}
private void postSavedWrite() {
handler.removeCallbacks(writeSaved); handler.removeCallbacks(writeSaved);
handler.postDelayed(writeSaved, 500); handler.postDelayed(writeSaved, 500);
bus.post(event);
} }
@Nonnull @Nonnull
@ -359,6 +391,15 @@ public class History {
addRecent(HistoryState.builder(editorState, displayState).build()); addRecent(HistoryState.builder(editorState, displayState).build());
} }
public boolean isLoaded() {
return loaded;
}
public void runWhenLoaded(@NonNull Runnable runnable) {
Check.isTrue(!loaded);
whenLoadedRunnables.add(runnable);
}
public static class ClearedEvent { public static class ClearedEvent {
public final boolean recent; public final boolean recent;
@ -406,6 +447,9 @@ public class History {
@Override @Override
public void run() { public void run() {
Check.isMainThread(); Check.isMainThread();
if (!loaded) {
return;
}
// don't need to save intermediate states, thus {@link History#getRecent} // don't need to save intermediate states, thus {@link History#getRecent}
final List<HistoryState> states = recent ? getRecent(false) : getSaved(); final List<HistoryState> states = recent ? getRecent(false) : getSaved();
backgroundThread.execute(new Runnable() { backgroundThread.execute(new Runnable() {

View File

@ -3,6 +3,7 @@ package org.solovyev.android.calculator.history;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.text.TextUtils;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -15,8 +16,6 @@ import org.solovyev.android.calculator.json.Jsonable;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static android.text.TextUtils.isEmpty;
public class HistoryState implements Parcelable, Jsonable { public class HistoryState implements Parcelable, Jsonable {
public static final Creator<HistoryState> CREATOR = new Creator<HistoryState>() { public static final Creator<HistoryState> CREATOR = new Creator<HistoryState>() {
@ -103,7 +102,7 @@ public class HistoryState implements Parcelable, Jsonable {
json.put(JSON_EDITOR, editor.toJson()); json.put(JSON_EDITOR, editor.toJson());
json.put(JSON_DISPLAY, display.toJson()); json.put(JSON_DISPLAY, display.toJson());
json.put(JSON_TIME, time); json.put(JSON_TIME, time);
if (!isEmpty(comment)) { if (!TextUtils.isEmpty(comment)) {
json.put(JSON_COMMENT, comment); json.put(JSON_COMMENT, comment);
} }
return json; return json;
@ -173,6 +172,10 @@ public class HistoryState implements Parcelable, Jsonable {
dest.writeString(comment); dest.writeString(comment);
} }
public boolean isEmpty() {
return display.isEmpty() && editor.isEmpty() && TextUtils.isEmpty(comment);
}
public static final class Builder { public static final class Builder {
@NonNull @NonNull