From b43ff0366247374be566237b925bf2a45f0dc9a6 Mon Sep 17 00:00:00 2001 From: Phillip Hsu Date: Fri, 29 Jul 2016 23:18:46 -0700 Subject: [PATCH] Abstracted SQL, Cursor, Loader, Adapter, RecyclerView --- app/src/main/AndroidManifest.xml | 8 +- .../philliphsu/clock2/BaseCursorAdapter.java | 78 +++++++++++ .../com/philliphsu/clock2/MainActivity.java | 19 +-- .../clock2/RecyclerViewFragment.java | 27 ++-- .../java/com/philliphsu/clock2/Timer.java | 33 +++-- .../clock2/alarms/AlarmsCursorAdapter.java | 2 +- .../clock2/edittimer/EditTimerActivity.java | 1 + .../clock2/model/BaseDatabaseHelper.java | 114 +++++++++++++++ .../clock2/model/BaseItemCursor.java | 38 +++++ .../clock2/model/NewSQLiteCursorLoader.java | 130 +++++++++++++++++ .../philliphsu/clock2/model/ObjectWithId.java | 18 +++ .../clock2/model/TimerDatabaseHelper.java | 132 ++++++++++++++++++ .../clock2/model/TimersListCursorLoader.java | 20 +++ .../clock2/timers/TimersCursorAdapter.java | 23 +++ .../clock2/timers/TimersFragment.java | 80 +++++------ app/src/main/res/values/strings.xml | 2 +- 16 files changed, 654 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/com/philliphsu/clock2/BaseCursorAdapter.java create mode 100644 app/src/main/java/com/philliphsu/clock2/model/BaseDatabaseHelper.java create mode 100644 app/src/main/java/com/philliphsu/clock2/model/BaseItemCursor.java create mode 100644 app/src/main/java/com/philliphsu/clock2/model/NewSQLiteCursorLoader.java create mode 100644 app/src/main/java/com/philliphsu/clock2/model/ObjectWithId.java create mode 100644 app/src/main/java/com/philliphsu/clock2/model/TimerDatabaseHelper.java create mode 100644 app/src/main/java/com/philliphsu/clock2/model/TimersListCursorLoader.java create mode 100644 app/src/main/java/com/philliphsu/clock2/timers/TimersCursorAdapter.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 759a47a..ab5cf5f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -79,7 +79,13 @@ android:exported="false"> - + + diff --git a/app/src/main/java/com/philliphsu/clock2/BaseCursorAdapter.java b/app/src/main/java/com/philliphsu/clock2/BaseCursorAdapter.java new file mode 100644 index 0000000..bb7575e --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/BaseCursorAdapter.java @@ -0,0 +1,78 @@ +package com.philliphsu.clock2; + +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.ViewGroup; + +import com.philliphsu.clock2.model.BaseItemCursor; +import com.philliphsu.clock2.model.ObjectWithId; + +/** + * Created by Phillip Hsu on 7/29/2016. + */ +public abstract class BaseCursorAdapter< + T extends ObjectWithId, + VH extends BaseViewHolder, + C extends BaseItemCursor> + extends RecyclerView.Adapter { + + private static final String TAG = "BaseCursorAdapter"; + + private final OnListItemInteractionListener mListener; + private C mCursor; + + protected abstract VH onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener listener); + + public BaseCursorAdapter(OnListItemInteractionListener listener) { + mListener = listener; + // Excerpt from docs of notifyDataSetChanged(): + // "RecyclerView will attempt to synthesize [artificially create?] + // visible structural change events [when items are inserted, removed or + // moved] for adapters that report that they have stable IDs when + // [notifyDataSetChanged()] is used. This can help for the purposes of + // animation and visual object persistence [?] but individual item views + // will still need to be rebound and relaid out." + setHasStableIds(true); + } + + /** + * not final to allow subclasses to use the viewType if needed + */ + @Override + public VH onCreateViewHolder(ViewGroup parent, int viewType) { + return onCreateViewHolder(parent, mListener); + } + + @Override + public final void onBindViewHolder(VH holder, int position) { + if (!mCursor.moveToPosition(position)) { + Log.e(TAG, "Failed to bind item at position " + position); + return; + } + holder.onBind(mCursor.getItem()); + } + + @Override + public final int getItemCount() { + return mCursor == null ? 0 : mCursor.getCount(); + } + + @Override + public final long getItemId(int position) { + if (mCursor == null || !mCursor.moveToPosition(position)) { + return super.getItemId(position); // -1 + } + return mCursor.getId(); + } + + public final void swapCursor(C cursor) { + if (mCursor == cursor) { + return; + } + if (mCursor != null) { + mCursor.close(); + } + mCursor = cursor; + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/MainActivity.java b/app/src/main/java/com/philliphsu/clock2/MainActivity.java index 2207f0c..ca8cece 100644 --- a/app/src/main/java/com/philliphsu/clock2/MainActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/MainActivity.java @@ -15,7 +15,6 @@ import android.view.ViewGroup; import android.widget.TextView; import com.philliphsu.clock2.alarms.AlarmsFragment; -import com.philliphsu.clock2.editalarm.EditAlarmActivity; import com.philliphsu.clock2.settings.SettingsActivity; import com.philliphsu.clock2.timers.TimersFragment; @@ -56,13 +55,17 @@ public class MainActivity extends BaseActivity { mFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - Intent intent = new Intent(MainActivity.this, EditAlarmActivity.class); - // Call Fragment#startActivityForResult() instead of Activity#startActivityForResult() - // because we want the result to be handled in the Fragment, not in this Activity. - // FragmentActivity does NOT deliver the result to the Fragment, i.e. your - // Fragment's onActivityResult() will NOT be called. - mSectionsPagerAdapter.getCurrentFragment() - .startActivityForResult(intent, AlarmsFragment.REQUEST_CREATE_ALARM); +// Intent intent = new Intent(MainActivity.this, EditAlarmActivity.class); +// // Call Fragment#startActivityForResult() instead of Activity#startActivityForResult() +// // because we want the result to be handled in the Fragment, not in this Activity. +// // FragmentActivity does NOT deliver the result to the Fragment, i.e. your +// // Fragment's onActivityResult() will NOT be called. +// mSectionsPagerAdapter.getCurrentFragment() +// .startActivityForResult(intent, AlarmsFragment.REQUEST_CREATE_ALARM); + Fragment f; + if ((f = mSectionsPagerAdapter.getCurrentFragment()) instanceof RecyclerViewFragment) { + ((RecyclerViewFragment) f).onFabClick(); + } } }); } diff --git a/app/src/main/java/com/philliphsu/clock2/RecyclerViewFragment.java b/app/src/main/java/com/philliphsu/clock2/RecyclerViewFragment.java index 5ad65d4..264d630 100644 --- a/app/src/main/java/com/philliphsu/clock2/RecyclerViewFragment.java +++ b/app/src/main/java/com/philliphsu/clock2/RecyclerViewFragment.java @@ -1,6 +1,5 @@ package com.philliphsu.clock2; -import android.database.Cursor; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; @@ -11,16 +10,20 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import com.philliphsu.clock2.model.BaseItemCursor; +import com.philliphsu.clock2.model.ObjectWithId; + import butterknife.Bind; /** * Created by Phillip Hsu on 7/26/2016. */ -public abstract class RecyclerViewFragment, - A extends BaseAdapter> // TODO: From AlarmsCursorAdapter, abstract it out and use that type here. + C extends BaseItemCursor, + A extends BaseCursorAdapter> extends BaseFragment implements - LoaderManager.LoaderCallbacks, + LoaderManager.LoaderCallbacks, OnListItemInteractionListener { private A mAdapter; @@ -45,6 +48,12 @@ public abstract class RecyclerViewFragment loader, Cursor data) { - // TODO: Change the adapter type to one that supports Cursors as its dataset -// mAdapter.swapCursor(data); + public void onLoadFinished(Loader loader, C data) { + mAdapter.swapCursor(data); } @Override - public void onLoaderReset(Loader loader) { - // TODO: Change the adapter type to one that supports Cursors as its dataset -// mAdapter.swapCursor(null); + public void onLoaderReset(Loader loader) { + mAdapter.swapCursor(null); } /** diff --git a/app/src/main/java/com/philliphsu/clock2/Timer.java b/app/src/main/java/com/philliphsu/clock2/Timer.java index b81c19d..5314ff1 100644 --- a/app/src/main/java/com/philliphsu/clock2/Timer.java +++ b/app/src/main/java/com/philliphsu/clock2/Timer.java @@ -3,6 +3,7 @@ package com.philliphsu.clock2; import android.os.SystemClock; import com.google.auto.value.AutoValue; +import com.philliphsu.clock2.model.ObjectWithId; import java.util.concurrent.TimeUnit; @@ -10,10 +11,9 @@ import java.util.concurrent.TimeUnit; * Created by Phillip Hsu on 7/25/2016. */ @AutoValue -public abstract class Timer { +public abstract class Timer extends ObjectWithId { private static final long MINUTE = TimeUnit.MINUTES.toMillis(1); - private long id; private long endTime; private long pauseTime; @@ -42,14 +42,6 @@ public abstract class Timer { return new AutoValue_Timer(hour, minute, second, group, label); } - public long id() { - return id; - } - - public void setId(long id) { - this.id = id; - } - public long endTime() { return endTime; } @@ -115,4 +107,25 @@ public abstract class Timer { public boolean isRunning() { return hasStarted() && pauseTime == 0; } + + /** + * TO ONLY BE CALLED BY TIMERDATABASEHELPER. + */ + public void setEndTime(long endTime) { + this.endTime = endTime; + } + + /** + * TO ONLY BE CALLED BY TIMERDATABASEHELPER. + */ + public void setPauseTime(long pauseTime) { + this.pauseTime = pauseTime; + } + + /** + * TO ONLY BE CALLED BY TIMERDATABASEHELPER. + */ + public long pauseTime() { + return pauseTime; + } } diff --git a/app/src/main/java/com/philliphsu/clock2/alarms/AlarmsCursorAdapter.java b/app/src/main/java/com/philliphsu/clock2/alarms/AlarmsCursorAdapter.java index c93787a..0d579d8 100644 --- a/app/src/main/java/com/philliphsu/clock2/alarms/AlarmsCursorAdapter.java +++ b/app/src/main/java/com/philliphsu/clock2/alarms/AlarmsCursorAdapter.java @@ -13,7 +13,7 @@ import com.philliphsu.clock2.util.AlarmController; /** * Created by Phillip Hsu on 6/29/2016. * - * TODO: Make this abstract for other data types. + * TODO: Extend from BaseCursorAdapter */ public class AlarmsCursorAdapter extends RecyclerView.Adapter { private static final String TAG = "AlarmsCursorAdapter"; diff --git a/app/src/main/java/com/philliphsu/clock2/edittimer/EditTimerActivity.java b/app/src/main/java/com/philliphsu/clock2/edittimer/EditTimerActivity.java index 0562f0e..d01b0c6 100644 --- a/app/src/main/java/com/philliphsu/clock2/edittimer/EditTimerActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/edittimer/EditTimerActivity.java @@ -153,6 +153,7 @@ public class EditTimerActivity extends BaseActivity { // TODO: Pass back an intent with the data, or make Timer parcelable // and pass back an instance of Timer. Consider overriding finish() // and doing it there. + setResult(RESULT_OK); finish(); } diff --git a/app/src/main/java/com/philliphsu/clock2/model/BaseDatabaseHelper.java b/app/src/main/java/com/philliphsu/clock2/model/BaseDatabaseHelper.java new file mode 100644 index 0000000..5537fba --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/model/BaseDatabaseHelper.java @@ -0,0 +1,114 @@ +package com.philliphsu.clock2.model; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.philliphsu.clock2.util.LocalBroadcastHelper; + +/** + * Created by Phillip Hsu on 7/29/2016. + */ +public abstract class BaseDatabaseHelper extends SQLiteOpenHelper { + + public static final String COLUMN_ID = "_id"; + + private final Context mAppContext; + + /** + * @param context the Context with which the application context will be retrieved + * @param name the name of the database file. Because this is required by the SQLiteOpenHelper + * constructor, we can't, for instance, have an abstract getDatabaseFileName() that + * subclasses implement and the base class can call on their behalf. + * @param version the version + */ + public BaseDatabaseHelper(Context context, String name, + /*SQLiteDatabase.CursorFactory factory,*/ + int version) { + super(context.getApplicationContext(), name, null, version); + mAppContext = context.getApplicationContext(); + } + + /** + * @return the table managed by this helper + */ + protected abstract String getTableName(); + + /** + * @return the ContentValues representing the item's properties. + * You do not need to put a mapping for {@link #COLUMN_ID}, since + * the database manages ids for us (if you created your table + * with {@link #COLUMN_ID} as an {@code INTEGER PRIMARY KEY}). + */ + protected abstract ContentValues toContentValues(T item); + + /** + * @return optional String specifying the sort order + * to use when querying the database. The default + * implementation returns null, which may return + * queries unordered. + */ + protected String getQuerySortOrder() { + return null; + } + + public long insertItem(T item) { + long id = getWritableDatabase().insert( + getTableName(), null, toContentValues(item)); + item.setId(id); + notifyContentChanged(); + return id; + } + + public int updateItem(long id, T newItem) { + newItem.setId(id); + SQLiteDatabase db = getWritableDatabase(); + int rowsUpdated = db.update(getTableName(), + toContentValues(newItem), + COLUMN_ID + " = " + id, + null); + notifyContentChanged(); + return rowsUpdated; + } + + public int deleteItem(T item) { + SQLiteDatabase db = getWritableDatabase(); + int rowsDeleted = db.delete(getTableName(), + COLUMN_ID + " = " + item.getId(), + null); + notifyContentChanged(); + return rowsDeleted; + } + + public Cursor queryItem(long id) { + return queryItems(COLUMN_ID + " = " + id, "1"); + } + + public Cursor queryItems() { + // Select all rows and columns + return queryItems(null, null); + } + + protected Cursor queryItems(String where, String limit) { + return getReadableDatabase().query(getTableName(), + null, // All columns + where, // Selection, i.e. where COLUMN_* = [value we're looking for] + null, // selection args, none b/c id already specified in selection + null, // group by + null, // having + getQuerySortOrder(), // order/sort by + limit); // limit + } + + /** + * Broadcasts to any registered receivers that the data backed + * by this helper has changed, and so they should requery and + * update themselves as necessary. + */ + private void notifyContentChanged() { + LocalBroadcastHelper.sendBroadcast(mAppContext, + SQLiteCursorLoader.ACTION_CHANGE_CONTENT); + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/model/BaseItemCursor.java b/app/src/main/java/com/philliphsu/clock2/model/BaseItemCursor.java new file mode 100644 index 0000000..269dcc4 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/model/BaseItemCursor.java @@ -0,0 +1,38 @@ +package com.philliphsu.clock2.model; + +import android.database.Cursor; +import android.database.CursorWrapper; +import android.util.Log; + +/** + * Created by Phillip Hsu on 7/29/2016. + */ +public abstract class BaseItemCursor extends CursorWrapper { + private static final String TAG = "BaseItemCursor"; + + public BaseItemCursor(Cursor cursor) { + super(cursor); + } + + /** + * @return an item instance configured for the current row, + * or null if the current row is invalid + */ + public abstract T getItem(); + + public long getId() { + if (isBeforeFirst() || isAfterLast()) { + Log.e(TAG, "Failed to retrieve id, cursor out of range"); + return -1; + } + return getLong(getColumnIndexOrThrow(BaseDatabaseHelper.COLUMN_ID)); + } + + /** + * Helper method to determine boolean-valued columns. + * SQLite does not support a BOOLEAN data type. + */ + protected boolean isTrue(String columnName) { + return getInt(getColumnIndexOrThrow(columnName)) == 1; + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/model/NewSQLiteCursorLoader.java b/app/src/main/java/com/philliphsu/clock2/model/NewSQLiteCursorLoader.java new file mode 100644 index 0000000..353dea0 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/model/NewSQLiteCursorLoader.java @@ -0,0 +1,130 @@ +package com.philliphsu.clock2.model; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.support.v4.content.AsyncTaskLoader; +import android.util.Log; + +import com.philliphsu.clock2.util.LocalBroadcastHelper; + +/** + * Created by Phillip Hsu on 6/28/2016. + * + * Efficiently loads and holds a Cursor. + */ +public abstract class NewSQLiteCursorLoader< + T extends ObjectWithId, + C extends BaseItemCursor> + extends AsyncTaskLoader { + private static final String TAG = "SQLiteCursorLoader"; + + public static final String ACTION_CHANGE_CONTENT = "com.philliphsu.clock2.model.action.CHANGE_CONTENT"; + + private C mCursor; + private OnContentChangeReceiver mOnContentChangeReceiver; + + public NewSQLiteCursorLoader(Context context) { + super(context); + } + + protected abstract C loadCursor(); + + /* Runs on a worker thread */ + @Override + public C loadInBackground() { + C cursor = loadCursor(); + if (cursor != null) { + // Ensure that the content window is filled + // Ensure that the data is available in memory once it is + // passed to the main thread + cursor.getCount(); + } + return cursor; + } + + /* Runs on the UI thread */ + @Override + public void deliverResult(C cursor) { + if (isReset()) { + // An async query came in while the loader is stopped + if (cursor != null) { + cursor.close(); + } + return; + } + Cursor oldCursor = mCursor; + mCursor = cursor; + + if (isStarted()) { + super.deliverResult(cursor); + } + + // Close the old cursor because it is no longer needed. + // Because an existing cursor may be cached and redelivered, it is important + // to make sure that the old cursor and the new cursor are not the + // same before the old cursor is closed. + if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) { + oldCursor.close(); + } + } + + // Refer to the docs if you wish to understand the rest of the API as used below. + + @Override + protected void onStartLoading() { + if (mCursor != null) { + deliverResult(mCursor); + } + + if (mOnContentChangeReceiver == null) { + mOnContentChangeReceiver = new OnContentChangeReceiver(); + LocalBroadcastHelper.registerReceiver(getContext(), + mOnContentChangeReceiver, ACTION_CHANGE_CONTENT); + } + + if (takeContentChanged() || mCursor == null) { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + // Attempt to cancel the current load task if possible. + cancelLoad(); + } + + @Override + public void onCanceled(C cursor) { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + @Override + protected void onReset() { + super.onReset(); + // Ensure the loader is stopped + onStopLoading(); + + if (mCursor != null && !mCursor.isClosed()) { + mCursor.close(); + } + mCursor = null; + + if (mOnContentChangeReceiver != null) { + LocalBroadcastHelper.unregisterReceiver(getContext(), + mOnContentChangeReceiver); + mOnContentChangeReceiver = null; + } + } + + private final class OnContentChangeReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Received content change event"); + onContentChanged(); + } + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/model/ObjectWithId.java b/app/src/main/java/com/philliphsu/clock2/model/ObjectWithId.java new file mode 100644 index 0000000..1bfbc54 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/model/ObjectWithId.java @@ -0,0 +1,18 @@ +package com.philliphsu.clock2.model; + +/** + * Created by Phillip Hsu on 7/29/2016. + * + * Superclass for objects that can be persisted in SQLite. + */ +public abstract class ObjectWithId { + private long id; + + public final long getId() { + return id; + } + + public final void setId(long id) { + this.id = id; + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/model/TimerDatabaseHelper.java b/app/src/main/java/com/philliphsu/clock2/model/TimerDatabaseHelper.java new file mode 100644 index 0000000..d3f23ec --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/model/TimerDatabaseHelper.java @@ -0,0 +1,132 @@ +package com.philliphsu.clock2.model; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.philliphsu.clock2.Timer; + +/** + * Created by Phillip Hsu on 7/29/2016. + */ +public class TimerDatabaseHelper extends BaseDatabaseHelper { + private static final String TAG = "TimerDatabaseHelper"; + private static final String DB_NAME = "timers.db"; + private static final int VERSION_1 = 1; + + private static final String TABLE_TIMERS = "timers"; + // TODO: Consider making these public, so we can move TimerCursor to its own top-level class. + private static final String COLUMN_HOUR = "hour"; + private static final String COLUMN_MINUTE = "minute"; + private static final String COLUMN_SECOND = "second"; + private static final String COLUMN_LABEL = "label"; + + // http://stackoverflow.com/q/24183958/5055032 + // https://www.sqlite.org/lang_keywords.html + // GROUP is a reserved keyword, so your CREATE TABLE statement + // will not compile if you include this! +// private static final String COLUMN_GROUP = "group"; + + private static final String COLUMN_END_TIME = "end_time"; + private static final String COLUMN_PAUSE_TIME = "pause_time"; + + private static final String SORT_ORDER = + COLUMN_HOUR + " ASC, " + + COLUMN_MINUTE + " ASC, " + + COLUMN_SECOND + " ASC, " + // All else equal, newer timers first + + COLUMN_ID + " DESC"; + + public TimerDatabaseHelper(Context context) { + super(context, DB_NAME, VERSION_1); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_TIMERS + " (" + + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COLUMN_HOUR + " INTEGER NOT NULL, " + + COLUMN_MINUTE + " INTEGER NOT NULL, " + + COLUMN_SECOND + " INTEGER NOT NULL, " + + COLUMN_LABEL + " TEXT NOT NULL, " +// + COLUMN_GROUP + " TEXT NOT NULL, " + + COLUMN_END_TIME + " INTEGER NOT NULL, " + + COLUMN_PAUSE_TIME + " INTEGER NOT NULL);"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // I don't think we need to drop current tables unless you make structural changes + // to the schema in the new version. + } + + // ============================================================================================= + // Overridden methods can have a more specific return type, as long as that type + // is a subtype of the original return type. + + @Override + public TimerCursor queryItem(long id) { + return wrapInTimerCursor(queryItem(id)); + } + + @Override + public TimerCursor queryItems() { + return wrapInTimerCursor(super.queryItems()); + } + + @Override + protected TimerCursor queryItems(String where, String limit) { + return wrapInTimerCursor(super.queryItems(where, limit)); + } + // ============================================================================================= + + @Override + protected String getTableName() { + return TABLE_TIMERS; + } + + @Override + protected ContentValues toContentValues(Timer timer) { + ContentValues cv = new ContentValues(); + cv.put(COLUMN_HOUR, timer.hour()); + cv.put(COLUMN_MINUTE, timer.minute()); + cv.put(COLUMN_SECOND, timer.second()); + cv.put(COLUMN_LABEL, timer.label()); +// cv.put(COLUMN_GROUP, timer.group()); + cv.put(COLUMN_END_TIME, timer.endTime()); + cv.put(COLUMN_PAUSE_TIME, timer.pauseTime()); + return cv; + } + + @Override + protected String getQuerySortOrder() { + return SORT_ORDER; + } + + private TimerCursor wrapInTimerCursor(Cursor c) { + return new TimerCursor(c); + } + + public static class TimerCursor extends BaseItemCursor { + + public TimerCursor(Cursor cursor) { + super(cursor); + } + + @Override + public Timer getItem() { + if (isBeforeFirst() || isAfterLast()) + return null; + int hour = getInt(getColumnIndexOrThrow(COLUMN_HOUR)); + int minute = getInt(getColumnIndexOrThrow(COLUMN_MINUTE)); + int second = getInt(getColumnIndexOrThrow(COLUMN_SECOND)); + String label = getString(getColumnIndexOrThrow(COLUMN_LABEL)); +// String group = getString(getColumnIndexOrThrow(COLUMN_GROUP)); + Timer t = Timer.create(hour, minute, second, label, /*group*/""); + t.setEndTime(getInt(getColumnIndexOrThrow(COLUMN_END_TIME))); + t.setPauseTime(getInt(getColumnIndexOrThrow(COLUMN_PAUSE_TIME))); + return t; + } + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/model/TimersListCursorLoader.java b/app/src/main/java/com/philliphsu/clock2/model/TimersListCursorLoader.java new file mode 100644 index 0000000..a41e028 --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/model/TimersListCursorLoader.java @@ -0,0 +1,20 @@ +package com.philliphsu.clock2.model; + +import android.content.Context; + +import com.philliphsu.clock2.Timer; + +/** + * Created by Phillip Hsu on 7/29/2016. + */ +public class TimersListCursorLoader extends NewSQLiteCursorLoader { + + public TimersListCursorLoader(Context context) { + super(context); + } + + @Override + protected TimerDatabaseHelper.TimerCursor loadCursor() { + return new TimerDatabaseHelper(getContext()).queryItems(); + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/timers/TimersCursorAdapter.java b/app/src/main/java/com/philliphsu/clock2/timers/TimersCursorAdapter.java new file mode 100644 index 0000000..9eacf5a --- /dev/null +++ b/app/src/main/java/com/philliphsu/clock2/timers/TimersCursorAdapter.java @@ -0,0 +1,23 @@ +package com.philliphsu.clock2.timers; + +import android.view.ViewGroup; + +import com.philliphsu.clock2.BaseCursorAdapter; +import com.philliphsu.clock2.OnListItemInteractionListener; +import com.philliphsu.clock2.Timer; +import com.philliphsu.clock2.model.TimerDatabaseHelper; + +/** + * Created by Phillip Hsu on 7/29/2016. + */ +public class TimersCursorAdapter extends BaseCursorAdapter { + + public TimersCursorAdapter(OnListItemInteractionListener listener) { + super(listener); + } + + @Override + protected TimerViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener listener) { + return new TimerViewHolder(parent, listener); + } +} diff --git a/app/src/main/java/com/philliphsu/clock2/timers/TimersFragment.java b/app/src/main/java/com/philliphsu/clock2/timers/TimersFragment.java index 1034504..df324b0 100644 --- a/app/src/main/java/com/philliphsu/clock2/timers/TimersFragment.java +++ b/app/src/main/java/com/philliphsu/clock2/timers/TimersFragment.java @@ -3,52 +3,52 @@ package com.philliphsu.clock2.timers; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; +import android.support.v4.content.Loader; -import com.philliphsu.clock2.OnListItemInteractionListener; -import com.philliphsu.clock2.R; +import com.philliphsu.clock2.RecyclerViewFragment; import com.philliphsu.clock2.Timer; import com.philliphsu.clock2.edittimer.EditTimerActivity; -import com.philliphsu.clock2.timers.dummy.DummyContent; - -import butterknife.ButterKnife; - -/** - * TODO: Extend from RecyclerViewFragment. - */ -public class TimersFragment extends Fragment { - - public TimersFragment() { - // Required empty public constructor - } +import com.philliphsu.clock2.model.TimerDatabaseHelper; +import com.philliphsu.clock2.model.TimersListCursorLoader; +public class TimersFragment extends RecyclerViewFragment< + Timer, + TimerViewHolder, + TimerDatabaseHelper.TimerCursor, + TimersCursorAdapter> { + public static final int REQUEST_CREATE_TIMER = 0; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_alarms, container, false); - ButterKnife.bind(this, view); - - RecyclerView rv = ButterKnife.findById(view, R.id.list); - rv.setLayoutManager(new LinearLayoutManager(getActivity())); - rv.setAdapter(new TimerAdapter(DummyContent.ITEMS, new OnListItemInteractionListener() { - @Override - public void onListItemClick(Timer item) { - startActivity(new Intent(getActivity(), EditTimerActivity.class)); - } - - @Override - public void onListItemDeleted(Timer item) { - - } - })); - - return view; + public void onActivityResult(int requestCode, int resultCode, Intent data) { +// if (resultCode != Activity.RESULT_OK || data == null) +// return; + TimerDatabaseHelper db = new TimerDatabaseHelper(getActivity()); + db.insertItem(Timer.create(1, 0, 0)); } + @Override + public void onFabClick() { + Intent intent = new Intent(getActivity(), EditTimerActivity.class); + startActivityForResult(intent, REQUEST_CREATE_TIMER); + } + + @Override + protected TimersCursorAdapter getAdapter() { + return new TimersCursorAdapter(this); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new TimersListCursorLoader(getActivity()); + } + + @Override + public void onListItemClick(Timer item) { + + } + + @Override + public void onListItemDeleted(Timer item) { + + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f37ce86..bdf5af4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -190,5 +190,5 @@ - Hello blank fragment + CreateTimerActivity