Abstracted SQL, Cursor, Loader, Adapter, RecyclerView
This commit is contained in:
parent
7c41ac2d29
commit
b43ff03662
@ -79,7 +79,13 @@
|
||||
android:exported="false">
|
||||
</service>
|
||||
|
||||
<activity android:name=".edittimer.EditTimerActivity">
|
||||
<activity android:name=".edittimer.EditTimerActivity"
|
||||
android:label="@string/title_activity_create_timer"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:windowSoftInputMode="adjustNothing">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.philliphsu.clock2.MainActivity"/>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
||||
@ -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<T>,
|
||||
C extends BaseItemCursor<T>>
|
||||
extends RecyclerView.Adapter<VH> {
|
||||
|
||||
private static final String TAG = "BaseCursorAdapter";
|
||||
|
||||
private final OnListItemInteractionListener<T> mListener;
|
||||
private C mCursor;
|
||||
|
||||
protected abstract VH onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<T> listener);
|
||||
|
||||
public BaseCursorAdapter(OnListItemInteractionListener<T> 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();
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -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<T,
|
||||
public abstract class RecyclerViewFragment<T extends ObjectWithId,
|
||||
VH extends BaseViewHolder<T>,
|
||||
A extends BaseAdapter<T, VH>> // TODO: From AlarmsCursorAdapter, abstract it out and use that type here.
|
||||
C extends BaseItemCursor<T>,
|
||||
A extends BaseCursorAdapter<T, VH, C>>
|
||||
extends BaseFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor>,
|
||||
LoaderManager.LoaderCallbacks<C>,
|
||||
OnListItemInteractionListener<T> {
|
||||
|
||||
private A mAdapter;
|
||||
@ -45,6 +48,12 @@ public abstract class RecyclerViewFragment<T,
|
||||
return new LinearLayoutManager(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
@ -55,15 +64,13 @@ public abstract class RecyclerViewFragment<T,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// TODO: Change the adapter type to one that supports Cursors as its dataset
|
||||
// mAdapter.swapCursor(data);
|
||||
public void onLoadFinished(Loader<C> loader, C data) {
|
||||
mAdapter.swapCursor(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// TODO: Change the adapter type to one that supports Cursors as its dataset
|
||||
// mAdapter.swapCursor(null);
|
||||
public void onLoaderReset(Loader<C> loader) {
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<AlarmViewHolder> {
|
||||
private static final String TAG = "AlarmsCursorAdapter";
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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<T extends ObjectWithId> 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);
|
||||
}
|
||||
}
|
||||
@ -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<T extends ObjectWithId> 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;
|
||||
}
|
||||
}
|
||||
@ -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<T>>
|
||||
extends AsyncTaskLoader<C> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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<Timer> {
|
||||
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<Timer> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Timer, TimerDatabaseHelper.TimerCursor> {
|
||||
|
||||
public TimersListCursorLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TimerDatabaseHelper.TimerCursor loadCursor() {
|
||||
return new TimerDatabaseHelper(getContext()).queryItems();
|
||||
}
|
||||
}
|
||||
@ -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<Timer, TimerViewHolder, TimerDatabaseHelper.TimerCursor> {
|
||||
|
||||
public TimersCursorAdapter(OnListItemInteractionListener<Timer> listener) {
|
||||
super(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TimerViewHolder onCreateViewHolder(ViewGroup parent, OnListItemInteractionListener<Timer> listener) {
|
||||
return new TimerViewHolder(parent, listener);
|
||||
}
|
||||
}
|
||||
@ -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 com.philliphsu.clock2.model.TimerDatabaseHelper;
|
||||
import com.philliphsu.clock2.model.TimersListCursorLoader;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
public class TimersFragment extends RecyclerViewFragment<
|
||||
Timer,
|
||||
TimerViewHolder,
|
||||
TimerDatabaseHelper.TimerCursor,
|
||||
TimersCursorAdapter> {
|
||||
public static final int REQUEST_CREATE_TIMER = 0;
|
||||
|
||||
/**
|
||||
* TODO: Extend from RecyclerViewFragment.
|
||||
*/
|
||||
public class TimersFragment extends Fragment {
|
||||
|
||||
public TimersFragment() {
|
||||
// Required empty public constructor
|
||||
@Override
|
||||
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
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_alarms, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
protected TimersCursorAdapter getAdapter() {
|
||||
return new TimersCursorAdapter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<TimerDatabaseHelper.TimerCursor> onCreateLoader(int id, Bundle args) {
|
||||
return new TimersListCursorLoader(getActivity());
|
||||
}
|
||||
|
||||
RecyclerView rv = ButterKnife.findById(view, R.id.list);
|
||||
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
rv.setAdapter(new TimerAdapter(DummyContent.ITEMS, new OnListItemInteractionListener<Timer>() {
|
||||
@Override
|
||||
public void onListItemClick(Timer item) {
|
||||
startActivity(new Intent(getActivity(), EditTimerActivity.class));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemDeleted(Timer item) {
|
||||
|
||||
}
|
||||
}));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -190,5 +190,5 @@
|
||||
</string>
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="title_activity_create_timer">CreateTimerActivity</string>
|
||||
</resources>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user