Abstracted SQL, Cursor, Loader, Adapter, RecyclerView
This commit is contained in:
parent
7c41ac2d29
commit
b43ff03662
@ -79,7 +79,13 @@
|
|||||||
android:exported="false">
|
android:exported="false">
|
||||||
</service>
|
</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>
|
</activity>
|
||||||
</application>
|
</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 android.widget.TextView;
|
||||||
|
|
||||||
import com.philliphsu.clock2.alarms.AlarmsFragment;
|
import com.philliphsu.clock2.alarms.AlarmsFragment;
|
||||||
import com.philliphsu.clock2.editalarm.EditAlarmActivity;
|
|
||||||
import com.philliphsu.clock2.settings.SettingsActivity;
|
import com.philliphsu.clock2.settings.SettingsActivity;
|
||||||
import com.philliphsu.clock2.timers.TimersFragment;
|
import com.philliphsu.clock2.timers.TimersFragment;
|
||||||
|
|
||||||
@ -56,13 +55,17 @@ public class MainActivity extends BaseActivity {
|
|||||||
mFab.setOnClickListener(new View.OnClickListener() {
|
mFab.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
Intent intent = new Intent(MainActivity.this, EditAlarmActivity.class);
|
// Intent intent = new Intent(MainActivity.this, EditAlarmActivity.class);
|
||||||
// Call Fragment#startActivityForResult() instead of Activity#startActivityForResult()
|
// // Call Fragment#startActivityForResult() instead of Activity#startActivityForResult()
|
||||||
// because we want the result to be handled in the Fragment, not in this Activity.
|
// // 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
|
// // FragmentActivity does NOT deliver the result to the Fragment, i.e. your
|
||||||
// Fragment's onActivityResult() will NOT be called.
|
// // Fragment's onActivityResult() will NOT be called.
|
||||||
mSectionsPagerAdapter.getCurrentFragment()
|
// mSectionsPagerAdapter.getCurrentFragment()
|
||||||
.startActivityForResult(intent, AlarmsFragment.REQUEST_CREATE_ALARM);
|
// .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;
|
package com.philliphsu.clock2;
|
||||||
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
@ -11,16 +10,20 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.philliphsu.clock2.model.BaseItemCursor;
|
||||||
|
import com.philliphsu.clock2.model.ObjectWithId;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Phillip Hsu on 7/26/2016.
|
* Created by Phillip Hsu on 7/26/2016.
|
||||||
*/
|
*/
|
||||||
public abstract class RecyclerViewFragment<T,
|
public abstract class RecyclerViewFragment<T extends ObjectWithId,
|
||||||
VH extends BaseViewHolder<T>,
|
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
|
extends BaseFragment implements
|
||||||
LoaderManager.LoaderCallbacks<Cursor>,
|
LoaderManager.LoaderCallbacks<C>,
|
||||||
OnListItemInteractionListener<T> {
|
OnListItemInteractionListener<T> {
|
||||||
|
|
||||||
private A mAdapter;
|
private A mAdapter;
|
||||||
@ -45,6 +48,12 @@ public abstract class RecyclerViewFragment<T,
|
|||||||
return new LinearLayoutManager(getActivity());
|
return new LinearLayoutManager(getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
getLoaderManager().initLoader(0, null, this);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
@ -55,15 +64,13 @@ public abstract class RecyclerViewFragment<T,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<C> loader, C data) {
|
||||||
// TODO: Change the adapter type to one that supports Cursors as its dataset
|
mAdapter.swapCursor(data);
|
||||||
// mAdapter.swapCursor(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoaderReset(Loader<Cursor> loader) {
|
public void onLoaderReset(Loader<C> loader) {
|
||||||
// TODO: Change the adapter type to one that supports Cursors as its dataset
|
mAdapter.swapCursor(null);
|
||||||
// mAdapter.swapCursor(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.philliphsu.clock2;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
|
import com.philliphsu.clock2.model.ObjectWithId;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -10,10 +11,9 @@ 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 {
|
public abstract class Timer extends ObjectWithId {
|
||||||
private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
|
private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
|
||||||
|
|
||||||
private long id;
|
|
||||||
private long endTime;
|
private long endTime;
|
||||||
private long pauseTime;
|
private long pauseTime;
|
||||||
|
|
||||||
@ -42,14 +42,6 @@ public abstract class Timer {
|
|||||||
return new AutoValue_Timer(hour, minute, second, group, label);
|
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() {
|
public long endTime() {
|
||||||
return endTime;
|
return endTime;
|
||||||
}
|
}
|
||||||
@ -115,4 +107,25 @@ public abstract class Timer {
|
|||||||
public boolean isRunning() {
|
public boolean isRunning() {
|
||||||
return hasStarted() && pauseTime == 0;
|
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.
|
* 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> {
|
public class AlarmsCursorAdapter extends RecyclerView.Adapter<AlarmViewHolder> {
|
||||||
private static final String TAG = "AlarmsCursorAdapter";
|
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
|
// TODO: Pass back an intent with the data, or make Timer parcelable
|
||||||
// and pass back an instance of Timer. Consider overriding finish()
|
// and pass back an instance of Timer. Consider overriding finish()
|
||||||
// and doing it there.
|
// and doing it there.
|
||||||
|
setResult(RESULT_OK);
|
||||||
finish();
|
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.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.content.Loader;
|
||||||
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 com.philliphsu.clock2.OnListItemInteractionListener;
|
import com.philliphsu.clock2.RecyclerViewFragment;
|
||||||
import com.philliphsu.clock2.R;
|
|
||||||
import com.philliphsu.clock2.Timer;
|
import com.philliphsu.clock2.Timer;
|
||||||
import com.philliphsu.clock2.edittimer.EditTimerActivity;
|
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Extend from RecyclerViewFragment.
|
|
||||||
*/
|
|
||||||
public class TimersFragment extends Fragment {
|
|
||||||
|
|
||||||
public TimersFragment() {
|
|
||||||
// Required empty public constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public class TimersFragment extends RecyclerViewFragment<
|
||||||
|
Timer,
|
||||||
|
TimerViewHolder,
|
||||||
|
TimerDatabaseHelper.TimerCursor,
|
||||||
|
TimersCursorAdapter> {
|
||||||
|
public static final int REQUEST_CREATE_TIMER = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
Bundle savedInstanceState) {
|
// if (resultCode != Activity.RESULT_OK || data == null)
|
||||||
View view = inflater.inflate(R.layout.fragment_alarms, container, false);
|
// return;
|
||||||
ButterKnife.bind(this, view);
|
TimerDatabaseHelper db = new TimerDatabaseHelper(getActivity());
|
||||||
|
db.insertItem(Timer.create(1, 0, 0));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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<TimerDatabaseHelper.TimerCursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new TimersListCursorLoader(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onListItemClick(Timer item) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onListItemDeleted(Timer item) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -190,5 +190,5 @@
|
|||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- TODO: Remove or change this placeholder text -->
|
<!-- 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>
|
</resources>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user