From d6eeac1db124c19fb15aa4436c2885e7f2bca965 Mon Sep 17 00:00:00 2001 From: Phillip Hsu Date: Mon, 12 Sep 2016 00:00:46 -0700 Subject: [PATCH] Wrote potentially unnecessary stuff --- .../stopwatch/BaseStopwatchController.java | 92 ++++++++++++++ .../clock2/stopwatch/Stopwatch.java | 55 ++++++++ .../clock2/stopwatch/StopwatchController.java | 10 -- .../clock2/stopwatch/StopwatchFragment.java | 119 ++++-------------- .../StopwatchNotificationService.java | 12 +- .../stopwatch/StopwatchViewController.java | 78 ++++++++++++ 6 files changed, 257 insertions(+), 109 deletions(-) create mode 100644 app/src/main/java/com/philliphsu/clock2/stopwatch/BaseStopwatchController.java create mode 100644 app/src/main/java/com/philliphsu/clock2/stopwatch/Stopwatch.java delete mode 100644 app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchController.java create mode 100644 app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchViewController.java diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/BaseStopwatchController.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/BaseStopwatchController.java new file mode 100644 index 0000000..1584428 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/BaseStopwatchController.java @@ -0,0 +1,92 @@ +package com.philliphsu.clock2.stopwatch; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; + +/** + * Created by Phillip Hsu on 9/11/2016. + */ +public abstract class BaseStopwatchController { // TODO: Extend this for use in StopwatchFragment and StopwatchNotificationSErvice + // TODO: EIther expose these to subclasses or write an API for them to call + // to write to prefs. + private static final String KEY_START_TIME = "start_time"; + private static final String KEY_PAUSE_TIME = "pause_time"; + private static final String KEY_RUNNING = "running"; + + private final AsyncLapsTableUpdateHandler mUpdateHandler; + private final SharedPreferences mPrefs; + + private Stopwatch mStopwatch; + private Lap mCurrentLap; + private Lap mPreviousLap; + + public BaseStopwatchController(@NonNull AsyncLapsTableUpdateHandler updateHandler, + @NonNull SharedPreferences prefs) { + mUpdateHandler = updateHandler; + mPrefs = prefs; + long startTime = mPrefs.getLong(KEY_START_TIME, 0); + long pauseTime = mPrefs.getLong(KEY_PAUSE_TIME, 0); + mStopwatch = new Stopwatch(startTime, pauseTime); + } + + public void run() { + if (!mStopwatch.hasStarted()) { + // addNewLap() won't call through unless chronometer is running, which + // we can't start until we compute mStartTime + mCurrentLap = new Lap(); + mUpdateHandler.asyncInsert(mCurrentLap); + } + mStopwatch.run(); + if (!mCurrentLap.isRunning()) { + mCurrentLap.resume(); + mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap); + } + savePrefs(); + } + + public void pause() { + mStopwatch.pause(); + mCurrentLap.pause(); + mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap); + savePrefs(); + } + + public void stop() { + mStopwatch.stop(); + mCurrentLap = null; + mPreviousLap = null; + mUpdateHandler.asyncClear(); // Clear laps + savePrefs(); + } + + public void addNewLap(String currentLapTotalText) { + if (mCurrentLap != null) { + mCurrentLap.end(currentLapTotalText); + } + mPreviousLap = mCurrentLap; + mCurrentLap = new Lap(); + if (mPreviousLap != null) { +// if (getAdapter().getItemCount() == 0) { +// mUpdateHandler.asyncInsert(mPreviousLap); +// } else { + mUpdateHandler.asyncUpdate(mPreviousLap.getId(), mPreviousLap); +// } + } + mUpdateHandler.asyncInsert(mCurrentLap); + } + + public final Stopwatch getStopwatch() { + return mStopwatch; + } + + public boolean isStopwatchRunning() { + return mPrefs.getBoolean(KEY_RUNNING, false); + } + + private void savePrefs() { + mPrefs.edit().putLong(KEY_START_TIME, mStopwatch.getStartTime()) + .putLong(KEY_PAUSE_TIME, mStopwatch.getPauseTime()) + .putBoolean(KEY_RUNNING, mStopwatch.isRunning()) + .apply(); + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/Stopwatch.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/Stopwatch.java new file mode 100644 index 0000000..6c93b73 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/Stopwatch.java @@ -0,0 +1,55 @@ +package com.philliphsu.clock2.stopwatch; + +import android.os.SystemClock; + +/** + * Created by Phillip Hsu on 9/11/2016. + */ +public final class Stopwatch { + private static final String TAG = "Stopwatch"; + + private long mStartTime; + private long mPauseTime; + + public Stopwatch(long startTime, long pauseTime) { + mStartTime = startTime; + mPauseTime = pauseTime; + } + + public synchronized void pause() { + if (!isRunning()) + throw new IllegalStateException("This stopwatch cannot be paused because it is not running"); + if (mPauseTime > 0) + throw new IllegalStateException("This stopwatch is already paused"); + mPauseTime = SystemClock.elapsedRealtime(); + } + + public synchronized void run() { + if (isRunning()) + throw new IllegalStateException("This stopwatch is already running"); + mStartTime += SystemClock.elapsedRealtime() - mPauseTime; + mPauseTime = 0; + } + + public synchronized void stop() { + mStartTime = 0; + mPauseTime = 0; + } + + public long getStartTime() { + return mStartTime; + } + + public long getPauseTime() { + return mPauseTime; + } + + public boolean isRunning() { + return hasStarted() && mPauseTime == 0; + } + + public boolean hasStarted() { + // Not required to be presently running to have been started + return mStartTime > 0; + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchController.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchController.java deleted file mode 100644 index e1594d0..0000000 --- a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchController.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.philliphsu.clock2.stopwatch; - -/** - * Created by Phillip Hsu on 9/11/2016. - */ -public final class StopwatchController { - - - -} diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java index 6468463..036b066 100644 --- a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchFragment.java @@ -6,7 +6,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; @@ -37,22 +36,17 @@ public class StopwatchFragment extends RecyclerViewFragment< LapsAdapter> { private static final String TAG = "StopwatchFragment"; - // Exposed for StopwatchNotificationService - static final String KEY_START_TIME = "start_time"; - static final String KEY_PAUSE_TIME = "pause_time"; - static final String KEY_CHRONOMETER_RUNNING = "chronometer_running"; - - private long mStartTime; - private long mPauseTime; - private Lap mCurrentLap; - private Lap mPreviousLap; - - private AsyncLapsTableUpdateHandler mUpdateHandler; private ObjectAnimator mProgressAnimator; - private SharedPreferences mPrefs; private WeakReference mActivityFab; private Drawable mStartDrawable; private Drawable mPauseDrawable; + // TODO: Actual subclass + private BaseStopwatchController mController; + + // For read-only purposes within this Fragment. + // Actual changes are persisted by the controller. + private Lap mCurrentLap; + private Lap mPreviousLap; @Bind(R.id.chronometer) ChronometerWithMillis mChronometer; @Bind(R.id.new_lap) FloatingActionButton mNewLapButton; @@ -67,12 +61,6 @@ public class StopwatchFragment extends RecyclerViewFragment< @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mUpdateHandler = new AsyncLapsTableUpdateHandler(getActivity(), null/*we shouldn't need a scroll handler*/); - mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); - mStartTime = mPrefs.getLong(KEY_START_TIME, 0); - mPauseTime = mPrefs.getLong(KEY_PAUSE_TIME, 0); - Log.d(TAG, "mStartTime = " + mStartTime - + ", mPauseTime = " + mPauseTime); // TODO: Will these be kept alive after onDestroyView()? If not, we should move these to // onCreateView() or any other callback that is guaranteed to be called. mStartDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_start_24dp); @@ -85,21 +73,13 @@ public class StopwatchFragment extends RecyclerViewFragment< Log.d(TAG, "onCreateView()"); View view = super.onCreateView(inflater, container, savedInstanceState); - mChronometer.setShowCentiseconds(true, true); - if (mStartTime > 0) { - long base = mStartTime; - if (mPauseTime > 0) { - base += SystemClock.elapsedRealtime() - mPauseTime; - // We're not done pausing yet, so don't reset mPauseTime. - } - mChronometer.setBase(base); - } - if (isStopwatchRunning()) { - mChronometer.start(); - // Note: mChronometer.isRunning() will return false at this point and - // in other upcoming lifecycle methods because it is not yet visible - // (i.e. mVisible == false). - } + AsyncLapsTableUpdateHandler updateHandler = new AsyncLapsTableUpdateHandler( + getActivity(), null/*we shouldn't need a scroll handler*/); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); + // This can't be initialized until the layout is inflated, because we need the + // mChronometer reference for the controller. + mController = new StopwatchViewController(updateHandler, prefs, mChronometer); + // The primary reason we call this is to show the mini FABs after rotate, // if the stopwatch is running. If the stopwatch is stopped, then this // would have hidden the mini FABs, if not for us already setting its @@ -158,9 +138,6 @@ public class StopwatchFragment extends RecyclerViewFragment< if (mProgressAnimator != null) { mProgressAnimator.removeAllListeners(); } - Log.d(TAG, "onDestroyView()"); - Log.d(TAG, "mStartTime = " + mStartTime - + ", mPauseTime = " + mPauseTime); } @Override @@ -203,7 +180,8 @@ public class StopwatchFragment extends RecyclerViewFragment< // // NOTE: If we just recreated ourselves due to rotation, mChronometer.isRunning() == false, // because it is not yet visible (i.e. mVisible == false). - if (isStopwatchRunning()) { + if (mController.isStopwatchRunning()) { + // TODO: I think we should just pass in the two laps as local params startNewProgressBarAnimator(); } else { // I verified the bar was visible already without this, so we probably don't need this, @@ -220,10 +198,7 @@ public class StopwatchFragment extends RecyclerViewFragment< @Override public void onFabClick() { if (mChronometer.isRunning()) { - mPauseTime = SystemClock.elapsedRealtime(); - mChronometer.stop(); - mCurrentLap.pause(); - mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap); + mController.pause(); // No issues controlling the animator here, because onLoadFinished() can't // call through to startNewProgressBarAnimator(), because by that point // the chronometer won't be running. @@ -234,20 +209,7 @@ public class StopwatchFragment extends RecyclerViewFragment< mProgressAnimator.cancel(); } } else { - if (mStartTime == 0) { - // addNewLap() won't call through unless chronometer is running, which - // we can't start until we compute mStartTime - mCurrentLap = new Lap(); - mUpdateHandler.asyncInsert(mCurrentLap); - } - mStartTime += SystemClock.elapsedRealtime() - mPauseTime; - mPauseTime = 0; - mChronometer.setBase(mStartTime); - mChronometer.start(); - if (!mCurrentLap.isRunning()) { - mCurrentLap.resume(); - mUpdateHandler.asyncUpdate(mCurrentLap.getId(), mCurrentLap); - } + mController.run(); // This animator instance will end up having end() called on it. When // the table update prompts us to requery, onLoadFinished will be called as a result. // There, it calls startNewProgressAnimator() to end this animation and starts an @@ -257,7 +219,6 @@ public class StopwatchFragment extends RecyclerViewFragment< // } getActivity().startService(new Intent(getActivity(), StopwatchNotificationService.class)); } - savePrefs(); // TOneverDO: Precede savePrefs(), or else we don't save false to KEY_CHRONOMETER_RUNNING /// and updateFab will update the wrong icon. updateAllFabs(); @@ -280,23 +241,7 @@ public class StopwatchFragment extends RecyclerViewFragment< @OnClick(R.id.new_lap) void addNewLap() { - if (!mChronometer.isRunning()) { - Log.d(TAG, "Cannot add new lap"); - return; - } - if (mCurrentLap != null) { - mCurrentLap.end(mChronometer.getText().toString()); - } - mPreviousLap = mCurrentLap; - mCurrentLap = new Lap(); - if (mPreviousLap != null) { -// if (getAdapter().getItemCount() == 0) { -// mUpdateHandler.asyncInsert(mPreviousLap); -// } else { - mUpdateHandler.asyncUpdate(mPreviousLap.getId(), mPreviousLap); -// } - } - mUpdateHandler.asyncInsert(mCurrentLap); + mController.addNewLap(mChronometer.getText().toString()); // This would end up being called twice: here, and in onLoadFinished(), because the // table updates will prompt us to requery. // startNewProgressBarAnimator(); @@ -304,17 +249,13 @@ public class StopwatchFragment extends RecyclerViewFragment< @OnClick(R.id.stop) void stop() { - mChronometer.stop(); - mChronometer.setBase(SystemClock.elapsedRealtime()); // ---------------------------------------------------------------------- // TOneverDO: Precede these with mProgressAnimator.end(), otherwise our // Animator.onAnimationEnd() callback won't hide SeekBar in time. - mStartTime = 0; - mPauseTime = 0; + mController.stop(); // ---------------------------------------------------------------------- mCurrentLap = null; mPreviousLap = null; - mUpdateHandler.asyncClear(); // Clear laps // No issues controlling the animator here, because onLoadFinished() can't // call through to startNewProgressBarAnimator(), because by that point // the chronometer won't be running. @@ -322,7 +263,6 @@ public class StopwatchFragment extends RecyclerViewFragment< mProgressAnimator.end(); } mProgressAnimator = null; - savePrefs(); // TOneverDO: Precede savePrefs(), or else we don't save false to KEY_CHRONOMETER_RUNNING /// and updateFab will update the wrong icon. updateAllFabs(); @@ -342,14 +282,14 @@ public class StopwatchFragment extends RecyclerViewFragment< } private void updateMiniFabs() { - boolean started = mStartTime > 0; + boolean started = mController.getStopwatch().hasStarted(); int vis = started ? View.VISIBLE : View.INVISIBLE; mNewLapButton.setVisibility(vis); mStopButton.setVisibility(vis); } private void updateFab() { - mActivityFab.get().setImageDrawable(isStopwatchRunning() ? mPauseDrawable : mStartDrawable); + mActivityFab.get().setImageDrawable(mController.isStopwatchRunning() ? mPauseDrawable : mStartDrawable); } private void startNewProgressBarAnimator() { @@ -414,21 +354,6 @@ public class StopwatchFragment extends RecyclerViewFragment< return mPreviousLap.elapsed() - mCurrentLap.elapsed(); } - private void savePrefs() { - mPrefs.edit().putLong(KEY_START_TIME, mStartTime) - .putLong(KEY_PAUSE_TIME, mPauseTime) - .putBoolean(KEY_CHRONOMETER_RUNNING, mChronometer.isRunning()) - .apply(); - } - - /** - * @return the state of the stopwatch when we're in a resumed and visible state, - * or when we're going through a rotation - */ - private boolean isStopwatchRunning() { - return mChronometer.isRunning() || mPrefs.getBoolean(KEY_CHRONOMETER_RUNNING, false); - } - // ======================= DO NOT IMPLEMENT ============================ @Override diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchNotificationService.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchNotificationService.java index fe470be..3175b0e 100644 --- a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchNotificationService.java +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchNotificationService.java @@ -68,10 +68,18 @@ public class StopwatchNotificationService extends ChronometerNotificationService @Override protected void handleStartPauseAction(Intent intent, int flags, long startId) { // TODO: Tell StopwatchFragment to start/pause itself.. perhaps with an Intent? - boolean running = mPrefs.getBoolean(StopwatchFragment.KEY_CHRONOMETER_RUNNING, false); + boolean running = mPrefs.getBoolean("KEY_RUNNING", false); syncNotificationWithStopwatchState(!running); SharedPreferences.Editor editor = mPrefs.edit(); - editor.putBoolean(StopwatchFragment.KEY_CHRONOMETER_RUNNING, !running); + editor.putBoolean("KEY_RUNNING", !running); + if (running) { + editor.putLong("key_pause_time", SystemClock.elapsedRealtime()); + } else { + long startTime = mPrefs.getLong("key_start_time", 0); + long pauseTime = mPrefs.getLong("key_pause_time", 0); + editor.putLong("key_start_time", startTime + SystemClock.elapsedRealtime() - pauseTime); + } + editor.apply(); } @Override diff --git a/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchViewController.java b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchViewController.java new file mode 100644 index 0000000..9786d0e --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/stopwatch/StopwatchViewController.java @@ -0,0 +1,78 @@ +package com.philliphsu.clock2.stopwatch; + +import android.content.SharedPreferences; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.util.Log; + +/** + * Created by Phillip Hsu on 9/11/2016. + */ +public final class StopwatchViewController extends BaseStopwatchController { + private static final String TAG = "StopwatchViewController"; + + private final ChronometerWithMillis mChronometer; + + public StopwatchViewController(@NonNull AsyncLapsTableUpdateHandler updateHandler, + @NonNull SharedPreferences prefs, + @NonNull ChronometerWithMillis chronometer) { + super(updateHandler, prefs); + mChronometer = chronometer; + + mChronometer.setShowCentiseconds(true, true); + final Stopwatch sw = getStopwatch(); + if (sw.getStartTime() > 0) { + long base = sw.getStartTime(); + if (sw.getPauseTime() > 0) { + base += SystemClock.elapsedRealtime() - sw.getPauseTime(); + // We're not done pausing yet, so don't reset mPauseTime. + } + chronometer.setBase(base); + } + if (isStopwatchRunning()) { + chronometer.start(); + // Note: mChronometer.isRunning() will return false at this point and + // in other upcoming lifecycle methods because it is not yet visible + // (i.e. mVisible == false). + } + } + + @Override + public void run() { + super.run(); + mChronometer.setBase(getStopwatch().getStartTime()); + mChronometer.start(); + } + + @Override + public void pause() { + // Keep this call and the call to Stopwatch.pause() close together to minimize the time delta. + mChronometer.stop(); + super.pause(); + } + + @Override + public void stop() { + mChronometer.stop(); + mChronometer.setBase(SystemClock.elapsedRealtime()); + super.stop(); + } + + @Override + public void addNewLap(String currentLapTotalText) { + if (!mChronometer.isRunning()) { + Log.d(TAG, "Cannot add new lap"); + return; + } + super.addNewLap(currentLapTotalText); + } + + /** + * @return the state of the stopwatch when we're in a resumed and visible state, + * or when we're going through a rotation + */ + @Override + public boolean isStopwatchRunning() { + return mChronometer.isRunning() || super.isStopwatchRunning(); + } +}