Timers persisted correctly, chronometer and buttons bind correctly

This commit is contained in:
Phillip Hsu 2016-08-02 17:25:14 -07:00
parent 992e091db7
commit 6f8d22f15b
15 changed files with 283 additions and 134 deletions

View File

@ -10,12 +10,10 @@ import com.philliphsu.clock2.util.AlarmController;
/** /**
* Created by Phillip Hsu on 7/1/2016. * Created by Phillip Hsu on 7/1/2016.
*
* TODO: Rename to AsyncAlarmChangeHandler
* TODO: Consider making an AsyncDatabaseChangeHandlerWithSnackbar abstract class * TODO: Consider making an AsyncDatabaseChangeHandlerWithSnackbar abstract class
*/ */
public final class AsyncItemChangeHandler extends AsyncDatabaseChangeHandler<Alarm, AlarmsTableManager> { public final class AsyncAlarmsTableUpdateHandler extends AsyncDatabaseTableUpdateHandler<Alarm, AlarmsTableManager> {
private static final String TAG = "AsyncItemChangeHandler"; private static final String TAG = "AsyncAlarmsTableUpdateHandler";
private final View mSnackbarAnchor; private final View mSnackbarAnchor;
private final AlarmController mAlarmController; private final AlarmController mAlarmController;
@ -24,9 +22,9 @@ public final class AsyncItemChangeHandler extends AsyncDatabaseChangeHandler<Ala
* @param context the Context from which we get the application context * @param context the Context from which we get the application context
* @param snackbarAnchor * @param snackbarAnchor
*/ */
public AsyncItemChangeHandler(Context context, View snackbarAnchor, public AsyncAlarmsTableUpdateHandler(Context context, View snackbarAnchor,
ScrollHandler scrollHandler, ScrollHandler scrollHandler,
AlarmController alarmController) { AlarmController alarmController) {
super(context, scrollHandler); super(context, scrollHandler);
mSnackbarAnchor = snackbarAnchor; mSnackbarAnchor = snackbarAnchor;
mAlarmController = alarmController; mAlarmController = alarmController;

View File

@ -10,10 +10,10 @@ import com.philliphsu.clock2.model.ObjectWithId;
/** /**
* Created by Phillip Hsu on 7/1/2016. * Created by Phillip Hsu on 7/1/2016.
*/ */
public abstract class AsyncDatabaseChangeHandler< public abstract class AsyncDatabaseTableUpdateHandler<
T extends ObjectWithId, T extends ObjectWithId,
TM extends DatabaseTableManager<T>> { TM extends DatabaseTableManager<T>> {
private static final String TAG = "AsyncDatabaseChangeHandler"; private static final String TAG = "AsyncDatabaseTableUpdateHandler";
private final Context mAppContext; private final Context mAppContext;
private final ScrollHandler mScrollHandler; private final ScrollHandler mScrollHandler;
@ -22,7 +22,7 @@ public abstract class AsyncDatabaseChangeHandler<
/** /**
* @param context the Context from which we get the application context * @param context the Context from which we get the application context
*/ */
public AsyncDatabaseChangeHandler(Context context, ScrollHandler scrollHandler) { public AsyncDatabaseTableUpdateHandler(Context context, ScrollHandler scrollHandler) {
mAppContext = context.getApplicationContext(); // to prevent memory leaks mAppContext = context.getApplicationContext(); // to prevent memory leaks
mScrollHandler = scrollHandler; mScrollHandler = scrollHandler;
mTableManager = getTableManager(context); mTableManager = getTableManager(context);

View File

@ -0,0 +1,36 @@
package com.philliphsu.clock2;
import android.content.Context;
import com.philliphsu.clock2.alarms.ScrollHandler;
import com.philliphsu.clock2.model.TimersTableManager;
/**
* Created by Phillip Hsu on 8/2/2016.
*/
public final class AsyncTimersTableUpdateHandler extends AsyncDatabaseTableUpdateHandler<Timer, TimersTableManager> {
public AsyncTimersTableUpdateHandler(Context context, ScrollHandler scrollHandler) {
super(context, scrollHandler);
}
@Override
protected TimersTableManager getTableManager(Context context) {
return new TimersTableManager(context);
}
@Override
protected void onPostAsyncDelete(Integer result, Timer timer) {
// TODO: Cancel the alarm scheduled for this timer
}
@Override
protected void onPostAsyncInsert(Long result, Timer timer) {
// TODO: if running, schedule alarm
}
@Override
protected void onPostAsyncUpdate(Long result, Timer timer) {
// TODO: cancel and reschedule
}
}

View File

@ -10,6 +10,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.philliphsu.clock2.alarms.ScrollHandler;
import com.philliphsu.clock2.model.BaseItemCursor; import com.philliphsu.clock2.model.BaseItemCursor;
import com.philliphsu.clock2.model.ObjectWithId; import com.philliphsu.clock2.model.ObjectWithId;
@ -25,9 +26,11 @@ public abstract class RecyclerViewFragment<
A extends BaseCursorAdapter<T, VH, C>> A extends BaseCursorAdapter<T, VH, C>>
extends BaseFragment implements extends BaseFragment implements
LoaderManager.LoaderCallbacks<C>, LoaderManager.LoaderCallbacks<C>,
OnListItemInteractionListener<T> { OnListItemInteractionListener<T>,
ScrollHandler {
private A mAdapter; private A mAdapter;
private long mScrollToStableId = RecyclerView.NO_ID;
// TODO: Rename id to recyclerView? // TODO: Rename id to recyclerView?
// TODO: Rename variable to mRecyclerView? // TODO: Rename variable to mRecyclerView?
@ -35,6 +38,14 @@ public abstract class RecyclerViewFragment<
public abstract void onFabClick(); public abstract void onFabClick();
/**
* Callback invoked when we have scrolled to the stable id as set in
* {@link #setScrollToStableId(long)}.
* @param id the stable id we have scrolled to
* @param position the position of the item with this stable id
*/
protected abstract void onScrolledToStableId(long id, int position);
/** /**
* @return the adapter to set on the RecyclerView. SUBCLASSES MUST OVERRIDE THIS, BECAUSE THE * @return the adapter to set on the RecyclerView. SUBCLASSES MUST OVERRIDE THIS, BECAUSE THE
* DEFAULT IMPLEMENTATION WILL ALWAYS RETURN AN UNINITIALIZED ADAPTER INSTANCE. * DEFAULT IMPLEMENTATION WILL ALWAYS RETURN AN UNINITIALIZED ADAPTER INSTANCE.
@ -71,6 +82,9 @@ public abstract class RecyclerViewFragment<
@Override @Override
public void onLoadFinished(Loader<C> loader, C data) { public void onLoadFinished(Loader<C> loader, C data) {
mAdapter.swapCursor(data); mAdapter.swapCursor(data);
// This may have been a requery due to content change. If the change
// was an insertion, scroll to the last modified alarm.
performScrollToStableId();
} }
@Override @Override
@ -86,4 +100,32 @@ public abstract class RecyclerViewFragment<
protected int contentLayout() { protected int contentLayout() {
return R.layout.fragment_recycler_view; return R.layout.fragment_recycler_view;
} }
@Override
public void setScrollToStableId(long id) {
mScrollToStableId = id;
}
@Override
public void scrollToPosition(int position) {
mList.smoothScrollToPosition(position);
}
private void performScrollToStableId() {
if (mScrollToStableId != RecyclerView.NO_ID) {
int position = -1;
for (int i = 0; i < mAdapter.getItemCount(); i++) {
if (mAdapter.getItemId(i) == mScrollToStableId) {
position = i;
break;
}
}
if (position >= 0) {
scrollToPosition(position);
onScrolledToStableId(mScrollToStableId, position);
}
}
// Reset
mScrollToStableId = RecyclerView.NO_ID;
}
} }

View File

@ -11,7 +11,7 @@ import java.util.concurrent.TimeUnit;
* Created by Phillip Hsu on 7/25/2016. * Created by Phillip Hsu on 7/25/2016.
*/ */
@AutoValue @AutoValue
public abstract class Timer extends ObjectWithId { public abstract class Timer extends ObjectWithId /*implements Parcelable*/ {
private static final long MINUTE = TimeUnit.MINUTES.toMillis(1); private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
private long endTime; private long endTime;
@ -128,4 +128,18 @@ public abstract class Timer extends ObjectWithId {
public long pauseTime() { public long pauseTime() {
return pauseTime; return pauseTime;
} }
// @Override
// public int describeContents() {
// return 0;
// }
//
// @Override
// public void writeToParcel(Parcel dest, int flags) {
// dest.writeInt(hour());
// dest.writeInt(minute());
// dest.writeInt(second());
// dest.writeString(group());
// dest.writeString(label());
// }
} }

View File

@ -6,12 +6,11 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v7.widget.RecyclerView;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import com.philliphsu.clock2.Alarm; import com.philliphsu.clock2.Alarm;
import com.philliphsu.clock2.AsyncItemChangeHandler; import com.philliphsu.clock2.AsyncAlarmsTableUpdateHandler;
import com.philliphsu.clock2.R; import com.philliphsu.clock2.R;
import com.philliphsu.clock2.RecyclerViewFragment; import com.philliphsu.clock2.RecyclerViewFragment;
import com.philliphsu.clock2.editalarm.EditAlarmActivity; import com.philliphsu.clock2.editalarm.EditAlarmActivity;
@ -20,8 +19,6 @@ import com.philliphsu.clock2.model.AlarmsListCursorLoader;
import com.philliphsu.clock2.util.AlarmController; import com.philliphsu.clock2.util.AlarmController;
import com.philliphsu.clock2.util.DelayedSnackbarHandler; import com.philliphsu.clock2.util.DelayedSnackbarHandler;
import butterknife.Bind;
public class AlarmsFragment extends RecyclerViewFragment< public class AlarmsFragment extends RecyclerViewFragment<
Alarm, Alarm,
BaseAlarmViewHolder, BaseAlarmViewHolder,
@ -34,14 +31,10 @@ public class AlarmsFragment extends RecyclerViewFragment<
public static final int REQUEST_CREATE_ALARM = 1; public static final int REQUEST_CREATE_ALARM = 1;
// private AlarmsCursorAdapter mAdapter; // private AlarmsCursorAdapter mAdapter;
// TODO: Since we only use this in onActivityResult(), we also don't need this anymore. private AsyncAlarmsTableUpdateHandler mAsyncAlarmsTableUpdateHandler;
private AsyncItemChangeHandler mAsyncItemChangeHandler;
private AlarmController mAlarmController; private AlarmController mAlarmController;
private Handler mHandler = new Handler(); private Handler mHandler = new Handler();
private View mSnackbarAnchor; private View mSnackbarAnchor;
private long mScrollToStableId = RecyclerView.NO_ID;
@Bind(R.id.list) RecyclerView mList;
/** /**
* Mandatory empty constructor for the fragment manager to instantiate the * Mandatory empty constructor for the fragment manager to instantiate the
@ -71,7 +64,7 @@ public class AlarmsFragment extends RecyclerViewFragment<
// See the Fragment lifecycle. // See the Fragment lifecycle.
mSnackbarAnchor = getActivity().findViewById(R.id.main_content); mSnackbarAnchor = getActivity().findViewById(R.id.main_content);
mAlarmController = new AlarmController(getActivity(), mSnackbarAnchor); mAlarmController = new AlarmController(getActivity(), mSnackbarAnchor);
mAsyncItemChangeHandler = new AsyncItemChangeHandler(getActivity(), mAsyncAlarmsTableUpdateHandler = new AsyncAlarmsTableUpdateHandler(getActivity(),
mSnackbarAnchor, this, mAlarmController); mSnackbarAnchor, this, mAlarmController);
} }
@ -91,10 +84,8 @@ public class AlarmsFragment extends RecyclerViewFragment<
@Override @Override
public void onLoadFinished(Loader<AlarmCursor> loader, AlarmCursor data) { public void onLoadFinished(Loader<AlarmCursor> loader, AlarmCursor data) {
super.onLoadFinished(loader, data); super.onLoadFinished(loader, data);
// This may have been a requery due to content change. If the change // TODO: If this was a content change due to an update, verify that
// was an insertion, scroll to the last modified alarm. // we scroll to the updated alarm if its sort order changes.
// TODO: If the change was an update, this presents a problem.
performScrollToStableId();
} }
@Override @Override
@ -108,7 +99,10 @@ public class AlarmsFragment extends RecyclerViewFragment<
protected AlarmsCursorAdapter getAdapter() { protected AlarmsCursorAdapter getAdapter() {
if (super.getAdapter() != null) if (super.getAdapter() != null)
return super.getAdapter(); return super.getAdapter();
// Create a new adapter // Create a new adapter. This is called before we can initialize mAlarmController,
// so right now it is null. However, after super.onCreate() returns, it is initialized, and
// the reference variable will be pointing to an actual object. This assignment "propagates"
// to all references to mAlarmController.
return new AlarmsCursorAdapter(this, mAlarmController); return new AlarmsCursorAdapter(this, mAlarmController);
} }
@ -132,19 +126,19 @@ public class AlarmsFragment extends RecyclerViewFragment<
switch (requestCode) { switch (requestCode) {
case REQUEST_CREATE_ALARM: case REQUEST_CREATE_ALARM:
mHandler.postDelayed( mHandler.postDelayed(
new AsyncAddItemRunnable(mAsyncItemChangeHandler, alarm), new AsyncAddItemRunnable(mAsyncAlarmsTableUpdateHandler, alarm),
300); 300);
break; break;
case REQUEST_EDIT_ALARM: case REQUEST_EDIT_ALARM:
if (data.getBooleanExtra(EditAlarmActivity.EXTRA_IS_DELETING, false)) { if (data.getBooleanExtra(EditAlarmActivity.EXTRA_IS_DELETING, false)) {
// TODO: Should we delay this too? It seems animations run // TODO: Should we delay this too? It seems animations run
// some of the time. // some of the time.
mAsyncItemChangeHandler.asyncDelete(alarm); mAsyncAlarmsTableUpdateHandler.asyncDelete(alarm);
} else { } else {
// TODO: Increase the delay, because update animation is // TODO: Increase the delay, because update animation is
// more elusive than insert. // more elusive than insert.
mHandler.postDelayed( mHandler.postDelayed(
new AsyncUpdateItemRunnable(mAsyncItemChangeHandler, alarm), new AsyncUpdateItemRunnable(mAsyncAlarmsTableUpdateHandler, alarm),
300); 300);
} }
break; break;
@ -165,12 +159,16 @@ public class AlarmsFragment extends RecyclerViewFragment<
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////////////
// TODO: Just like with TimersCursorAdapter, we could pass in the mAsyncAlarmsTableUpdateHandler
// to the AlarmsCursorAdapter and call these on the save and delete button click bindings.
@Override @Override
// TODO: Rename to onListItem***Delete*** because the item hasn't been deleted from our db yet // TODO: Rename to onListItem***Delete*** because the item hasn't been deleted from our db yet
public void onListItemDeleted(final Alarm item) { public void onListItemDeleted(final Alarm item) {
// The corresponding VH will be automatically removed from view following // The corresponding VH will be automatically removed from view following
// the requery, so we don't have to do anything to it. // the requery, so we don't have to do anything to it.
mAsyncItemChangeHandler.asyncDelete(item); mAsyncAlarmsTableUpdateHandler.asyncDelete(item);
} }
@Override @Override
@ -182,70 +180,54 @@ public class AlarmsFragment extends RecyclerViewFragment<
// TODO: Implement editing in the expanded VH. Then verify that changes // TODO: Implement editing in the expanded VH. Then verify that changes
// while in that VH are saved and updated after the requery. // while in that VH are saved and updated after the requery.
// getAdapter().collapse(position); // getAdapter().collapse(position);
mAsyncItemChangeHandler.asyncUpdate(item.getId(), item); mAsyncAlarmsTableUpdateHandler.asyncUpdate(item.getId(), item);
} }
/////////////////////////////////////////////////////////////////////////////////////////////////
@Override @Override
public void setScrollToStableId(long id) { protected void onScrolledToStableId(long id, int position) {
mScrollToStableId = id; // We were called because of a requery. If it was due to an insertion,
} // expand the newly added alarm.
boolean expanded = getAdapter().expand(position);
@Override if (!expanded) {
public void scrollToPosition(int position) { // Otherwise, it was due to an item update. The VH is expanded
mList.smoothScrollToPosition(position); // at this point, so reset it.
} getAdapter().collapse(position);
private void performScrollToStableId() {
if (mScrollToStableId != RecyclerView.NO_ID) {
int position = -1;
for (int i = 0; i < getAdapter().getItemCount(); i++) {
if (getAdapter().getItemId(i) == mScrollToStableId) {
position = i;
break;
}
}
if (position >= 0) {
scrollToPosition(position);
// We were called because of a requery. If it was due to an insertion,
// expand the newly added alarm.
boolean expanded = getAdapter().expand(position);
if (!expanded) {
// Otherwise, it was due to an item update. The VH is expanded
// at this point, so reset it.
getAdapter().collapse(position);
}
}
} }
// Reset
mScrollToStableId = RecyclerView.NO_ID;
} }
/////////////////////////////////////////////////////////////////////////////////////
// TODO: We won't need these anymore, since we won't handle the db
// update in onActivityResult() anymore.
@Deprecated
private static abstract class BaseAsyncItemChangeRunnable { private static abstract class BaseAsyncItemChangeRunnable {
// TODO: Will holding onto this cause a memory leak? // TODO: Will holding onto this cause a memory leak?
private final AsyncItemChangeHandler mAsyncItemChangeHandler; private final AsyncAlarmsTableUpdateHandler mAsyncAlarmsTableUpdateHandler;
private final Alarm mAlarm; private final Alarm mAlarm;
BaseAsyncItemChangeRunnable(AsyncItemChangeHandler asyncItemChangeHandler, Alarm alarm) { BaseAsyncItemChangeRunnable(AsyncAlarmsTableUpdateHandler asyncAlarmsTableUpdateHandler, Alarm alarm) {
mAsyncItemChangeHandler = asyncItemChangeHandler; mAsyncAlarmsTableUpdateHandler = asyncAlarmsTableUpdateHandler;
mAlarm = alarm; mAlarm = alarm;
} }
void asyncAddAlarm() { void asyncAddAlarm() {
mAsyncItemChangeHandler.asyncInsert(mAlarm); mAsyncAlarmsTableUpdateHandler.asyncInsert(mAlarm);
} }
void asyncUpdateAlarm() { void asyncUpdateAlarm() {
mAsyncItemChangeHandler.asyncUpdate(mAlarm.getId(), mAlarm); mAsyncAlarmsTableUpdateHandler.asyncUpdate(mAlarm.getId(), mAlarm);
} }
void asyncRemoveAlarm() { void asyncRemoveAlarm() {
mAsyncItemChangeHandler.asyncDelete(mAlarm); mAsyncAlarmsTableUpdateHandler.asyncDelete(mAlarm);
} }
} }
private static class AsyncAddItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable { private static class AsyncAddItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable {
AsyncAddItemRunnable(AsyncItemChangeHandler asyncItemChangeHandler, Alarm alarm) { AsyncAddItemRunnable(AsyncAlarmsTableUpdateHandler asyncAlarmsTableUpdateHandler, Alarm alarm) {
super(asyncItemChangeHandler, alarm); super(asyncAlarmsTableUpdateHandler, alarm);
} }
@Override @Override
@ -255,8 +237,8 @@ public class AlarmsFragment extends RecyclerViewFragment<
} }
private static class AsyncUpdateItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable { private static class AsyncUpdateItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable {
AsyncUpdateItemRunnable(AsyncItemChangeHandler asyncItemChangeHandler, Alarm alarm) { AsyncUpdateItemRunnable(AsyncAlarmsTableUpdateHandler asyncAlarmsTableUpdateHandler, Alarm alarm) {
super(asyncItemChangeHandler, alarm); super(asyncAlarmsTableUpdateHandler, alarm);
} }
@Override @Override
@ -266,8 +248,8 @@ public class AlarmsFragment extends RecyclerViewFragment<
} }
private static class AsyncRemoveItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable { private static class AsyncRemoveItemRunnable extends BaseAsyncItemChangeRunnable implements Runnable {
AsyncRemoveItemRunnable(AsyncItemChangeHandler asyncItemChangeHandler, Alarm alarm) { AsyncRemoveItemRunnable(AsyncAlarmsTableUpdateHandler asyncAlarmsTableUpdateHandler, Alarm alarm) {
super(asyncItemChangeHandler, alarm); super(asyncAlarmsTableUpdateHandler, alarm);
} }
@Override @Override

View File

@ -1,5 +1,6 @@
package com.philliphsu.clock2.edittimer; package com.philliphsu.clock2.edittimer;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.v7.widget.GridLayout; import android.support.v7.widget.GridLayout;
@ -21,6 +22,11 @@ import butterknife.OnTouch;
// TODO: Rename to CreateTimerActivity // TODO: Rename to CreateTimerActivity
public class EditTimerActivity extends BaseActivity { public class EditTimerActivity extends BaseActivity {
private static final int FIELD_LENGTH = 2; private static final int FIELD_LENGTH = 2;
public static final String EXTRA_HOUR = "com.philliphsu.clock2.edittimer.extra.HOUR";
public static final String EXTRA_MINUTE = "com.philliphsu.clock2.edittimer.extra.MINUTE";
public static final String EXTRA_SECOND = "com.philliphsu.clock2.edittimer.extra.SECOND";
public static final String EXTRA_LABEL = "com.philliphsu.clock2.edittimer.extra.LABEL";
public static final String EXTRA_START_TIMER = "com.philliphsu.clock2.edittimer.extra.START_TIMER";
@Bind(R.id.appbar) ViewGroup mAppBar; @Bind(R.id.appbar) ViewGroup mAppBar;
@Bind(R.id.label) TextView mLabel; @Bind(R.id.label) TextView mLabel;
@ -148,12 +154,15 @@ public class EditTimerActivity extends BaseActivity {
int second = Integer.parseInt(mSecond.getText().toString()); int second = Integer.parseInt(mSecond.getText().toString());
if (hour == 0 && minute == 0 && second == 0) if (hour == 0 && minute == 0 && second == 0)
return; // TODO: we could show a toast instead if we cared return; // TODO: we could show a toast instead if we cared
// TODO: do something with the label // TODO: Consider overriding finish() and doing this there.
mLabel.getText(); // TODO: Timer's group?
// TODO: Pass back an intent with the data, or make Timer parcelable Intent data = new Intent()
// and pass back an instance of Timer. Consider overriding finish() .putExtra(EXTRA_HOUR, hour)
// and doing it there. .putExtra(EXTRA_MINUTE, minute)
setResult(RESULT_OK); .putExtra(EXTRA_SECOND, second)
.putExtra(EXTRA_LABEL, mLabel.getText().toString())
.putExtra(EXTRA_START_TIMER, true);
setResult(RESULT_OK, data);
finish(); finish();
} }

View File

@ -73,6 +73,9 @@ public abstract class DatabaseTableManager<T extends ObjectWithId> {
toContentValues(newItem), toContentValues(newItem),
COLUMN_ID + " = " + id, COLUMN_ID + " = " + id,
null); null);
if (rowsUpdated == 0) {
throw new IllegalStateException("wtf?");
}
notifyContentChanged(); notifyContentChanged();
return rowsUpdated; return rowsUpdated;
} }

View File

@ -23,8 +23,9 @@ public class TimerCursor extends BaseItemCursor<Timer> {
String label = getString(getColumnIndexOrThrow(TimersTable.COLUMN_LABEL)); String label = getString(getColumnIndexOrThrow(TimersTable.COLUMN_LABEL));
// String group = getString(getColumnIndexOrThrow(COLUMN_GROUP)); // String group = getString(getColumnIndexOrThrow(COLUMN_GROUP));
Timer t = Timer.create(hour, minute, second, label, /*group*/""); Timer t = Timer.create(hour, minute, second, label, /*group*/"");
t.setEndTime(getInt(getColumnIndexOrThrow(TimersTable.COLUMN_END_TIME))); t.setId(getLong(getColumnIndexOrThrow(TimersTable.COLUMN_ID)));
t.setPauseTime(getInt(getColumnIndexOrThrow(TimersTable.COLUMN_PAUSE_TIME))); t.setEndTime(getLong(getColumnIndexOrThrow(TimersTable.COLUMN_END_TIME)));
t.setPauseTime(getLong(getColumnIndexOrThrow(TimersTable.COLUMN_PAUSE_TIME)));
return t; return t;
} }
} }

View File

@ -3,6 +3,7 @@ package com.philliphsu.clock2.model;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.util.Log;
import com.philliphsu.clock2.Timer; import com.philliphsu.clock2.Timer;
@ -10,6 +11,7 @@ import com.philliphsu.clock2.Timer;
* Created by Phillip Hsu on 7/30/2016. * Created by Phillip Hsu on 7/30/2016.
*/ */
public class TimersTableManager extends DatabaseTableManager<Timer> { public class TimersTableManager extends DatabaseTableManager<Timer> {
public static final String TAG = "TimersTableManager";
public TimersTableManager(Context context) { public TimersTableManager(Context context) {
super(context); super(context);
@ -22,7 +24,7 @@ public class TimersTableManager extends DatabaseTableManager<Timer> {
@Override @Override
public TimerCursor queryItem(long id) { public TimerCursor queryItem(long id) {
return wrapInTimerCursor(queryItem(id)); return wrapInTimerCursor(super.queryItem(id));
} }
@Override @Override
@ -48,6 +50,7 @@ public class TimersTableManager extends DatabaseTableManager<Timer> {
cv.put(TimersTable.COLUMN_SECOND, timer.second()); cv.put(TimersTable.COLUMN_SECOND, timer.second());
cv.put(TimersTable.COLUMN_LABEL, timer.label()); cv.put(TimersTable.COLUMN_LABEL, timer.label());
// cv.put(TimersTable.COLUMN_GROUP, timer.group()); // cv.put(TimersTable.COLUMN_GROUP, timer.group());
Log.d(TAG, "endTime = " + timer.endTime() + ", pauseTime = " + timer.pauseTime());
cv.put(TimersTable.COLUMN_END_TIME, timer.endTime()); cv.put(TimersTable.COLUMN_END_TIME, timer.endTime());
cv.put(TimersTable.COLUMN_PAUSE_TIME, timer.pauseTime()); cv.put(TimersTable.COLUMN_PAUSE_TIME, timer.pauseTime());
return cv; return cv;

View File

@ -11,6 +11,7 @@ import java.util.List;
/** /**
* Created by Phillip Hsu on 7/26/2016. * Created by Phillip Hsu on 7/26/2016.
*/ */
@Deprecated
public class TimerAdapter extends BaseAdapter<Timer, TimerViewHolder> { public class TimerAdapter extends BaseAdapter<Timer, TimerViewHolder> {
public TimerAdapter(List<Timer> items, OnListItemInteractionListener<Timer> listener) { public TimerAdapter(List<Timer> items, OnListItemInteractionListener<Timer> listener) {
@ -19,7 +20,7 @@ public class TimerAdapter extends BaseAdapter<Timer, TimerViewHolder> {
@Override @Override
protected TimerViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener) { protected TimerViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener) {
return new TimerViewHolder(parent, listener); return new TimerViewHolder(parent, listener, null);
} }
@Override @Override

View File

@ -1,7 +1,5 @@
package com.philliphsu.clock2.timers; package com.philliphsu.clock2.timers;
import android.os.SystemClock;
import android.view.View;
import android.widget.ImageButton; import android.widget.ImageButton;
import com.philliphsu.clock2.Timer; import com.philliphsu.clock2.Timer;
@ -23,55 +21,57 @@ public class TimerController {
mAddOneMinute = addOneMinute; mAddOneMinute = addOneMinute;
mStartPause = startPause; mStartPause = startPause;
mStop = stop; mStop = stop;
init();
// init();
} }
private void init() { // private void init() {
mChronometer.setBase(SystemClock.elapsedRealtime() + mTimer.duration()); // mChronometer.setBase(SystemClock.elapsedRealtime() + mTimer.duration());
updateStartPauseIcon(); // updateStartPauseIcon();
setSecondaryButtonsVisible(false); // setSecondaryButtonsVisible(false);
} // }
public void start() { public void start() {
mTimer.start(); mTimer.start();
mChronometer.setBase(mTimer.endTime()); // mChronometer.setBase(mTimer.endTime());
mChronometer.start(); // mChronometer.start();
updateStartPauseIcon(); // updateStartPauseIcon();
setSecondaryButtonsVisible(true); // setSecondaryButtonsVisible(true);
} }
public void pause() { public void pause() {
mTimer.pause(); mTimer.pause();
mChronometer.stop(); // mChronometer.stop();
updateStartPauseIcon(); // updateStartPauseIcon();
} }
public void resume() { public void resume() {
mTimer.resume(); mTimer.resume();
mChronometer.setBase(mTimer.endTime()); // mChronometer.setBase(mTimer.endTime());
mChronometer.start(); // mChronometer.start();
updateStartPauseIcon(); // updateStartPauseIcon();
} }
public void stop() { public void stop() {
mTimer.stop(); mTimer.stop();
mChronometer.stop(); // mChronometer.stop();
init(); // init();
} }
public void addOneMinute() { public void addOneMinute() {
mTimer.addOneMinute(); mTimer.addOneMinute();
mChronometer.setBase(mTimer.endTime()); // mChronometer.setBase(mTimer.endTime());
} }
public void updateStartPauseIcon() { // public void updateStartPauseIcon() {
// TODO: Pause and start icons, resp. // // TODO: Pause and start icons, resp.
// mStartPause.setImageResource(mTimer.isRunning() ? 0 : 0); // mStartPause.setImageResource(mTimer.isRunning() ? 0 : 0);
} // }
public void setSecondaryButtonsVisible(boolean visible) { // public void setSecondaryButtonsVisible(boolean visible) {
int visibility = visible ? View.VISIBLE : View.INVISIBLE; // int visibility = visible ? View.VISIBLE : View.INVISIBLE;
mAddOneMinute.setVisibility(visibility); // mAddOneMinute.setVisibility(visibility);
mStop.setVisibility(visibility); // mStop.setVisibility(visibility);
} // }
} }

View File

@ -1,10 +1,13 @@
package com.philliphsu.clock2.timers; package com.philliphsu.clock2.timers;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
import com.philliphsu.clock2.BaseViewHolder; import com.philliphsu.clock2.BaseViewHolder;
import com.philliphsu.clock2.OnListItemInteractionListener; import com.philliphsu.clock2.OnListItemInteractionListener;
import com.philliphsu.clock2.R; import com.philliphsu.clock2.R;
@ -17,8 +20,10 @@ import butterknife.OnClick;
* Created by Phillip Hsu on 7/25/2016. * Created by Phillip Hsu on 7/25/2016.
*/ */
public class TimerViewHolder extends BaseViewHolder<Timer> { public class TimerViewHolder extends BaseViewHolder<Timer> {
private static final String TAG = "TimerViewHolder";
private TimerController mController; // private TimerController mController;
private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler;
@Bind(R.id.label) TextView mLabel; @Bind(R.id.label) TextView mLabel;
@Bind(R.id.duration) CountdownChronometer mChronometer; @Bind(R.id.duration) CountdownChronometer mChronometer;
@ -28,17 +33,19 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
@Bind(R.id.stop) ImageButton mStop; @Bind(R.id.stop) ImageButton mStop;
// TODO: Controller param // TODO: Controller param
public TimerViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener) { public TimerViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener,
AsyncTimersTableUpdateHandler asyncTimersTableUpdateHandler) {
super(parent, R.layout.item_timer, listener); super(parent, R.layout.item_timer, listener);
mAsyncTimersTableUpdateHandler = asyncTimersTableUpdateHandler;
} }
@Override @Override
public void onBind(Timer timer) { public void onBind(Timer timer) {
super.onBind(timer); super.onBind(timer);
bindLabel(timer.label()); bindLabel(timer.label());
// We can't create the controller until this VH binds, because // // We can't create the controller until this VH binds, because
// the widgets only exist after this point. // // the widgets only exist after this point.
mController = new TimerController(timer, mChronometer, mAddOneMinute, mStartPause, mStop); // mController = new TimerController(timer, mChronometer, mAddOneMinute, mStartPause, mStop);
bindChronometer(timer); bindChronometer(timer);
bindButtonControls(timer); bindButtonControls(timer);
} }
@ -47,24 +54,30 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
void startPause() { void startPause() {
Timer t = getItem(); Timer t = getItem();
if (t.isRunning()) { if (t.isRunning()) {
mController.pause(); // mController.pause();
t.pause();
} else { } else {
if (t.hasStarted()) { if (t.hasStarted()) {
mController.resume(); t.resume();
} else { } else {
mController.start(); t.start();
} }
} }
// Persist value changes
update();
} }
@OnClick(R.id.add_one_minute) @OnClick(R.id.add_one_minute)
void addOneMinute() { void addOneMinute() {
mController.addOneMinute(); getItem().addOneMinute();
// Persist end time increase
update();
} }
@OnClick(R.id.stop) @OnClick(R.id.stop)
void stop() { void stop() {
mController.stop(); getItem().stop();
update();
} }
private void bindLabel(String label) { private void bindLabel(String label) {
@ -85,9 +98,7 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
if (!timer.hasStarted()) { if (!timer.hasStarted()) {
// Set the initial text // Set the initial text
// TODO: Verify the controller should already have initialized mChronometer.setDuration(timer.duration());
// the text when it was constructed.
// mChronometer.setDuration(timer.duration());
} else if (timer.isRunning()) { } else if (timer.isRunning()) {
// Re-initialize the base // Re-initialize the base
mChronometer.setBase(timer.endTime()); mChronometer.setBase(timer.endTime());
@ -107,7 +118,19 @@ public class TimerViewHolder extends BaseViewHolder<Timer> {
} }
private void bindButtonControls(Timer timer) { private void bindButtonControls(Timer timer) {
mController.updateStartPauseIcon(); // TODO: Pause and start icons, resp.
mController.setSecondaryButtonsVisible(timer.hasStarted()); // mStartPause.setImageResource(timer.isRunning() ? 0 : 0);
int visibility = timer.hasStarted() ? View.VISIBLE : View.INVISIBLE;
mAddOneMinute.setVisibility(visibility);
mStop.setVisibility(visibility);
}
private void update() {
Timer t = getItem();
mAsyncTimersTableUpdateHandler.asyncUpdate(
// Alternatively, use ViewHolder#getItemId() because we can forget
// to set the id on the object in BaseItemCursor#getItem(). We
// luckily remembered to this time!
t.getId(), t);
} }
} }

View File

@ -2,6 +2,7 @@ package com.philliphsu.clock2.timers;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
import com.philliphsu.clock2.BaseCursorAdapter; import com.philliphsu.clock2.BaseCursorAdapter;
import com.philliphsu.clock2.OnListItemInteractionListener; import com.philliphsu.clock2.OnListItemInteractionListener;
import com.philliphsu.clock2.Timer; import com.philliphsu.clock2.Timer;
@ -12,12 +13,18 @@ import com.philliphsu.clock2.model.TimerCursor;
*/ */
public class TimersCursorAdapter extends BaseCursorAdapter<Timer, TimerViewHolder, TimerCursor> { public class TimersCursorAdapter extends BaseCursorAdapter<Timer, TimerViewHolder, TimerCursor> {
public TimersCursorAdapter(OnListItemInteractionListener<Timer> listener) { private final AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler;
public TimersCursorAdapter(OnListItemInteractionListener<Timer> listener,
AsyncTimersTableUpdateHandler asyncTimersTableUpdateHandler) {
super(listener); super(listener);
mAsyncTimersTableUpdateHandler = asyncTimersTableUpdateHandler;
} }
@Override @Override
protected TimerViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener, int viewType) { protected TimerViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener, int viewType) {
return new TimerViewHolder(parent, listener); return new TimerViewHolder(parent, listener, mAsyncTimersTableUpdateHandler);
} }
} }

View File

@ -1,11 +1,13 @@
package com.philliphsu.clock2.timers; package com.philliphsu.clock2.timers;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import com.philliphsu.clock2.AsyncTimersTableUpdateHandler;
import com.philliphsu.clock2.RecyclerViewFragment; import com.philliphsu.clock2.RecyclerViewFragment;
import com.philliphsu.clock2.Timer; import com.philliphsu.clock2.Timer;
import com.philliphsu.clock2.edittimer.EditTimerActivity; import com.philliphsu.clock2.edittimer.EditTimerActivity;
@ -19,10 +21,30 @@ public class TimersFragment extends RecyclerViewFragment<
TimersCursorAdapter> { TimersCursorAdapter> {
public static final int REQUEST_CREATE_TIMER = 0; public static final int REQUEST_CREATE_TIMER = 0;
private AsyncTimersTableUpdateHandler mAsyncTimersTableUpdateHandler;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAsyncTimersTableUpdateHandler = new AsyncTimersTableUpdateHandler(getActivity(), this);
}
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
// if (resultCode != Activity.RESULT_OK || data == null) if (resultCode != Activity.RESULT_OK || data == null)
// return; return;
int hour = data.getIntExtra(EditTimerActivity.EXTRA_HOUR, -1);
int minute = data.getIntExtra(EditTimerActivity.EXTRA_MINUTE, -1);
int second = data.getIntExtra(EditTimerActivity.EXTRA_SECOND, -1);
String label = data.getStringExtra(EditTimerActivity.EXTRA_LABEL);
boolean startTimer = data.getBooleanExtra(EditTimerActivity.EXTRA_START_TIMER, false);
// TODO: Timer's group?
Timer t = Timer.createWithLabel(hour, minute, second, label);
if (startTimer) {
t.start();
}
mAsyncTimersTableUpdateHandler.asyncInsert(t);
} }
@Override @Override
@ -36,8 +58,11 @@ public class TimersFragment extends RecyclerViewFragment<
protected TimersCursorAdapter getAdapter() { protected TimersCursorAdapter getAdapter() {
if (super.getAdapter() != null) if (super.getAdapter() != null)
return super.getAdapter(); return super.getAdapter();
// Create a new adapter // Create a new adapter. This is called before we can initialize mAsyncTimersTableUpdateHandler,
return new TimersCursorAdapter(this); // so right now it is null. However, after super.onCreate() returns, it is initialized, and
// the reference variable will be pointing to an actual object. This assignment "propagates"
// to all references to mAsyncTimersTableUpdateHandler.
return new TimersCursorAdapter(this, mAsyncTimersTableUpdateHandler);
} }
@Override @Override
@ -59,4 +84,9 @@ public class TimersFragment extends RecyclerViewFragment<
public void onListItemUpdate(Timer item, int position) { public void onListItemUpdate(Timer item, int position) {
} }
@Override
protected void onScrolledToStableId(long id, int position) {
}
} }