diff --git a/app/src/main/java/com/philliphsu/clock2/Alarm.java b/app/src/main/java/com/philliphsu/clock2/Alarm.java index 0e99096..beacf50 100644 --- a/app/src/main/java/com/philliphsu/clock2/Alarm.java +++ b/app/src/main/java/com/philliphsu/clock2/Alarm.java @@ -70,6 +70,13 @@ public abstract class Alarm implements JsonSerializable { return true; } + /** ONLY CALL THIS WHEN CREATING AN ALARM INSTANCE FROM A CURSOR */ + // TODO: To be even more safe, create a ctor that takes the two Cursors and + // initialize the instance here instead of in AlarmDatabaseHelper. + public void setSnoozing(long snoozingUntilMillis) { + this.snoozingUntilMillis = snoozingUntilMillis; + } + public void stopSnoozing() { snoozingUntilMillis = 0; } diff --git a/app/src/main/java/com/philliphsu/clock2/SharedPreferencesHelper.java b/app/src/main/java/com/philliphsu/clock2/SharedPreferencesHelper.java index fa2dfc1..839fe1e 100644 --- a/app/src/main/java/com/philliphsu/clock2/SharedPreferencesHelper.java +++ b/app/src/main/java/com/philliphsu/clock2/SharedPreferencesHelper.java @@ -5,6 +5,7 @@ import android.support.annotation.StringRes; /** * Created by Phillip Hsu on 6/6/2016. */ +@Deprecated public interface SharedPreferencesHelper { /** Suitable for retrieving the value of a ListPreference */ int getInt(@StringRes int key, int defaultValue); diff --git a/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmUtilsHelper.java b/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmUtilsHelper.java index 3fec9d2..e5be364 100644 --- a/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmUtilsHelper.java +++ b/app/src/main/java/com/philliphsu/clock2/editalarm/AlarmUtilsHelper.java @@ -5,6 +5,7 @@ import com.philliphsu.clock2.Alarm; /** * Created by Phillip Hsu on 6/3/2016. */ +@Deprecated public interface AlarmUtilsHelper { void scheduleAlarm(Alarm alarm); void cancelAlarm(Alarm alarm, boolean showToast); diff --git a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java index 5a8a2b8..9483186 100644 --- a/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java +++ b/app/src/main/java/com/philliphsu/clock2/editalarm/EditAlarmActivity.java @@ -28,6 +28,7 @@ import com.philliphsu.clock2.BaseActivity; import com.philliphsu.clock2.DaysOfWeek; import com.philliphsu.clock2.R; import com.philliphsu.clock2.SharedPreferencesHelper; +import com.philliphsu.clock2.model.DatabaseManager; import com.philliphsu.clock2.ringtone.RingtoneActivity; import com.philliphsu.clock2.util.AlarmUtils; import com.philliphsu.clock2.util.LocalBroadcastHelper; @@ -48,7 +49,7 @@ import static com.philliphsu.clock2.util.KeyboardUtils.hideKeyboard; import static com.philliphsu.clock2.util.Preconditions.checkNotNull; public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyListener, - EditAlarmContract.View, + EditAlarmContract.View, // TODO: Remove @Override from the methods AlarmUtilsHelper, SharedPreferencesHelper { private static final String TAG = "EditAlarmActivity"; @@ -59,7 +60,8 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi private static final int ID_MENU_ITEM = 0; private Uri mSelectedRingtoneUri; - private Alarm mAlarm; + private Alarm mOldAlarm; + private DatabaseManager mDatabaseManager; @Bind(R.id.save) Button mSave; @Bind(R.id.delete) Button mDelete; @@ -79,50 +81,21 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi mNumpad.setKeyListener(this); loadAlarm(getIntent().getLongExtra(EXTRA_ALARM_ID, -1)); setTimeTextHint(); // TODO: private access - } - - private void loadAlarm(long alarmId) { - mAlarm = alarmId > -1 ? - null // TODO: Retrieve from SQLite - : null; - showDetails(); - } - - // TODO: Privatize access of each method called here. - private void showDetails() { - if (mAlarm != null) { - showTime(mAlarm.hour(), mAlarm.minutes()); - showEnabled(mAlarm.isEnabled()); - for (int i = SUNDAY; i <= SATURDAY; i++) { - showRecurringDays(i, mAlarm.isRecurring(i)); - } - showLabel(mAlarm.label()); - showRingtone(mAlarm.ringtone()); - showVibrates(mAlarm.vibrates()); - // Editing so don't show - showNumpad(false); - showTimeTextFocused(false); - } else { - // TODO default values - showTimeTextFocused(true); - showRingtone(""); // gets default ringtone - showNumpad(true); - } + mDatabaseManager = DatabaseManager.getInstance(this); } @Override public boolean onPrepareOptionsMenu(Menu menu) { // TODO: Snooze menu item when alarm is ringing. - if (mAlarm != null && mAlarm.isEnabled()) { - // TODO: Read from prefs directly? + if (mOldAlarm != null && mOldAlarm.isEnabled()) { int hoursBeforeUpcoming = getInt(R.string.key_notify_me_of_upcoming_alarms, 2); // TODO: Schedule task with handler to show the menu item when it is time. Handler is fine because // the task only needs to be done if the activity is being viewed. (I think) if the process of this // app is killed, then the handler is also killed. - if ((mAlarm.ringsWithinHours(hoursBeforeUpcoming))) { + if ((mOldAlarm.ringsWithinHours(hoursBeforeUpcoming))) { showCanDismissNow(); - } else if (mAlarm.isSnoozed()) { - showSnoozed(new Date(mAlarm.snoozingUntil())); + } else if (mOldAlarm.isSnoozed()) { + showSnoozed(new Date(mOldAlarm.snoozingUntil())); } } return super.onPrepareOptionsMenu(menu); @@ -133,10 +106,9 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi switch (item.getItemId()) { case R.id.action_dismiss_now: case R.id.action_done_snoozing: - // TODO: helper method? - cancelAlarm(checkNotNull(mAlarm), true); + cancelAlarm(checkNotNull(mOldAlarm), true); // cancelAlarm() should have turned off this alarm if appropriate - showEnabled(mAlarm.isEnabled()); + showEnabled(mOldAlarm.isEnabled()); item.setVisible(false); // This only matters for case R.id.action_done_snoozing. // It won't hurt to call this for case R.id.action_dismiss_now. @@ -236,32 +208,30 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi return; } - Alarm a = Alarm.builder() + Alarm alarm = Alarm.builder() .hour(hour) .minutes(minutes) .ringtone(getRingtone()) .label(getLabel()) .vibrates(vibrates()) .build(); - a.setEnabled(isEnabled()); + alarm.setEnabled(isEnabled()); for (int i = SUNDAY; i <= SATURDAY; i++) { - a.setRecurring(i, isRecurringDay(i)); + alarm.setRecurring(i, isRecurringDay(i)); } - if (mAlarm != null) { - if (mAlarm.isEnabled()) { + if (mOldAlarm != null) { + if (mOldAlarm.isEnabled()) { Log.d(TAG, "Cancelling old alarm first"); - cancelAlarm(mAlarm, false); + cancelAlarm(mOldAlarm, false); } - // TODO: Update sql - //mRepository.updateItem(mAlarm, a); + mDatabaseManager.updateAlarm(mOldAlarm, alarm); } else { - // TODO: Insert sql - //mRepository.addItem(a); + mDatabaseManager.insertAlarm(alarm); } - if (a.isEnabled()) { - scheduleAlarm(a); + if (alarm.isEnabled()) { + scheduleAlarm(alarm); } showEditorClosed(); @@ -270,12 +240,11 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi // TODO: Private accessor @OnClick(R.id.delete) void delete() { - if (mAlarm != null) { - if (mAlarm.isEnabled()) { - cancelAlarm(mAlarm, false); + if (mOldAlarm != null) { + if (mOldAlarm.isEnabled()) { + cancelAlarm(mOldAlarm, false); } - // TODO: Delete sql - //mRepository.deleteItem(mAlarm); + mDatabaseManager.deleteAlarm(mOldAlarm); } showEditorClosed(); } @@ -291,7 +260,6 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi if (mTimeText.length() == 0 || mNumpad.checkTimeValid()) { return false; // proceed to call through } else { - // TODO: is there anything to MVP? Toast.makeText(this, "Enter a valid time first.", Toast.LENGTH_SHORT).show(); return true; // capture and end the touch event here } @@ -310,7 +278,6 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi @OnCheckedChanged(R.id.on_off) void onChecked(boolean checked) { if (checked && mTimeText.length() == 0) { - // TODO: is there anything to MVP? mNumpad.setTime(0, 0); } } @@ -328,7 +295,6 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi } private void setWeekDaysText() { - // TODO: is there anything to MVP? for (int i = 0; i < mDays.length; i++) { int weekDay = DaysOfWeek.getInstance(this).weekDayAt(i); String label = DaysOfWeek.getLabel(weekDay); @@ -534,4 +500,31 @@ public class EditAlarmActivity extends BaseActivity implements AlarmNumpad.KeyLi public int getInt(@StringRes int key, int defaultValue) { return AlarmUtils.readPreference(this, key, defaultValue); } + + private void loadAlarm(long alarmId) { + mOldAlarm = alarmId > -1 ? mDatabaseManager.getAlarm(alarmId) : null; + showDetails(); + } + + // TODO: Privatize access of each method called here. + private void showDetails() { + if (mOldAlarm != null) { + showTime(mOldAlarm.hour(), mOldAlarm.minutes()); + showEnabled(mOldAlarm.isEnabled()); + for (int i = SUNDAY; i <= SATURDAY; i++) { + showRecurringDays(i, mOldAlarm.isRecurring(i)); + } + showLabel(mOldAlarm.label()); + showRingtone(mOldAlarm.ringtone()); + showVibrates(mOldAlarm.vibrates()); + // Editing so don't show + showNumpad(false); + showTimeTextFocused(false); + } else { + // TODO default values + showTimeTextFocused(true); + showRingtone(""); // gets default ringtone + showNumpad(true); + } + } } diff --git a/app/src/main/java/com/philliphsu/clock2/model/AlarmDatabaseHelper.java b/app/src/main/java/com/philliphsu/clock2/model/AlarmDatabaseHelper.java index 5d94a79..fcff5b0 100644 --- a/app/src/main/java/com/philliphsu/clock2/model/AlarmDatabaseHelper.java +++ b/app/src/main/java/com/philliphsu/clock2/model/AlarmDatabaseHelper.java @@ -2,11 +2,20 @@ package com.philliphsu.clock2.model; import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; +import android.database.CursorWrapper; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import com.philliphsu.clock2.Alarm; -import com.philliphsu.clock2.DaysOfWeek; + +import static com.philliphsu.clock2.DaysOfWeek.FRIDAY; +import static com.philliphsu.clock2.DaysOfWeek.MONDAY; +import static com.philliphsu.clock2.DaysOfWeek.SATURDAY; +import static com.philliphsu.clock2.DaysOfWeek.SUNDAY; +import static com.philliphsu.clock2.DaysOfWeek.THURSDAY; +import static com.philliphsu.clock2.DaysOfWeek.TUESDAY; +import static com.philliphsu.clock2.DaysOfWeek.WEDNESDAY; /** * Created by Phillip Hsu on 6/24/2016. @@ -19,6 +28,8 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper { // TODO: Consider creating an inner class that implements BaseColumns // and defines all the columns. + // TODO: Consider statically defining index constants for each column, + // and then removing all cursor getColumnIndex() calls. private static final String TABLE_ALARMS = "alarms"; private static final String COLUMN_ID = "_id"; private static final String COLUMN_HOUR = "hour"; @@ -41,6 +52,17 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper { private static final String COLUMN_FRIDAY = "friday"; private static final String COLUMN_SATURDAY = "saturday"; + // Statically define column indices for the days so we don't have + // to call cursor.getColumnIndex(COLUMN_[DAY]). We offset the value + // by one because COLUMN_ALARM_ID is actually first. + private static final int INDEX_SUNDAY = SUNDAY + 1; + private static final int INDEX_MONDAY = MONDAY + 1; + private static final int INDEX_TUESDAY = TUESDAY + 1; + private static final int INDEX_WEDNESDAY = WEDNESDAY + 1; + private static final int INDEX_THURSDAY = THURSDAY + 1; + private static final int INDEX_FRIDAY = FRIDAY + 1; + private static final int INDEX_SATURDAY = SATURDAY + 1; + private static void createAlarmsTable(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + TABLE_ALARMS + " (" + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " @@ -89,6 +111,75 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper { // TODO: Consider changing param to Alarm.Builder so the Alarm that will be // built can have its ID set. public long insertAlarm(Alarm alarm) { + long id = getWritableDatabase().insert(TABLE_ALARMS, null, toContentValues(alarm)); + alarm.setId(id); + getWritableDatabase().insert(TABLE_ALARM_RECURRING_DAYS, null, toRecurrenceContentValues(id, alarm)); + return id; + } + + public int updateAlarm(Alarm oldAlarm, Alarm newAlarm) { + newAlarm.setId(oldAlarm.getId()); + SQLiteDatabase db = getWritableDatabase(); + int rowsUpdatedInAlarmsTable = db.update(TABLE_ALARMS, + toContentValues(newAlarm), + COLUMN_ID + " = " + newAlarm.getId(), + null); + int rowsUpdatedInRecurrencesTable = db.update(TABLE_ALARM_RECURRING_DAYS, + toRecurrenceContentValues(newAlarm.getId(), newAlarm), + COLUMN_ALARM_ID + " = " + newAlarm.getId(), + null); + if (rowsUpdatedInAlarmsTable == rowsUpdatedInRecurrencesTable && rowsUpdatedInAlarmsTable == 1) { + return 1; + } + throw new IllegalStateException("rows updated in TABLE_ALARMS = " + rowsUpdatedInAlarmsTable + + ", rows updated in TABLE_ALARM_RECURRING_DAYS = " + rowsUpdatedInRecurrencesTable); + } + + public int deleteAlarm(Alarm alarm) { + SQLiteDatabase db = getWritableDatabase(); + int rowsDeletedInAlarmsTable = db.delete(TABLE_ALARMS, + COLUMN_ID + " = " + alarm.getId(), + null); + int rowsDeletedInRecurrencesTable = db.delete(TABLE_ALARM_RECURRING_DAYS, + COLUMN_ALARM_ID + " = " + alarm.getId(), + null); + if (rowsDeletedInAlarmsTable == rowsDeletedInRecurrencesTable && rowsDeletedInAlarmsTable == 1) { + return 1; + } + throw new IllegalStateException("rows deleted in TABLE_ALARMS = " + rowsDeletedInAlarmsTable + + ", rows deleted in TABLE_ALARM_RECURRING_DAYS = " + rowsDeletedInRecurrencesTable); + } + + public AlarmCursor queryAlarm(long id) { + Cursor alarmCursor = getReadableDatabase().query(TABLE_ALARMS, + null, // All columns + COLUMN_ID + " = " + id, // Selection for this alarm id + null, // selection args, none b/c id already specified in selection + null, // group by + null, // order/sort by + null, // having + "1"); // limit 1 row + Cursor recurrenceCursor = getReadableDatabase().query(TABLE_ALARM_RECURRING_DAYS, + null, // All columns + COLUMN_ID + " = " + id, // selection + null, // selection args + null, // group by + null, // order by + null, // having + "1"); + return new AlarmCursor(alarmCursor, recurrenceCursor); + } + + public AlarmCursor queryAlarms() { + // Select all rows and columns from both tables + Cursor alarmCursor = getReadableDatabase().query(TABLE_ALARMS, + null, null, null, null, null, null); + Cursor recurrenceCursor = getReadableDatabase().query(TABLE_ALARM_RECURRING_DAYS, + null, null, null, null, null, null); + return new AlarmCursor(alarmCursor, recurrenceCursor); + } + + private ContentValues toContentValues(Alarm alarm) { ContentValues values = new ContentValues(); values.put(COLUMN_HOUR, alarm.hour()); values.put(COLUMN_MINUTES, alarm.minutes()); @@ -97,19 +188,62 @@ public class AlarmDatabaseHelper extends SQLiteOpenHelper { values.put(COLUMN_VIBRATES, alarm.vibrates()); values.put(COLUMN_ENABLED, alarm.isEnabled()); values.put(COLUMN_SNOOZING_UNTIL_MILLIS, alarm.snoozingUntil()); - long id = getWritableDatabase().insert(TABLE_ALARMS, null, values); - alarm.setId(id); + return values; + } - values = new ContentValues(); - values.put(COLUMN_ALARM_ID, id); - values.put(COLUMN_SUNDAY, alarm.isRecurring(DaysOfWeek.SUNDAY)); - values.put(COLUMN_MONDAY, alarm.isRecurring(DaysOfWeek.MONDAY)); - values.put(COLUMN_TUESDAY, alarm.isRecurring(DaysOfWeek.TUESDAY)); - values.put(COLUMN_WEDNESDAY, alarm.isRecurring(DaysOfWeek.WEDNESDAY)); - values.put(COLUMN_THURSDAY, alarm.isRecurring(DaysOfWeek.THURSDAY)); - values.put(COLUMN_FRIDAY, alarm.isRecurring(DaysOfWeek.FRIDAY)); - values.put(COLUMN_SATURDAY, alarm.isRecurring(DaysOfWeek.SATURDAY)); - getWritableDatabase().insert(TABLE_ALARM_RECURRING_DAYS, null, values); - return id; + // Even though the current impl of insertAlarm() sets the given id on the alarm, we require it + // as a param just as an extra measure, in case you happen to not set it. + private ContentValues toRecurrenceContentValues(long id, Alarm alarm) { + ContentValues values = new ContentValues(); + values.put(COLUMN_ALARM_ID, id); // *could* have used alarm.id() instead + values.put(COLUMN_SUNDAY, alarm.isRecurring(SUNDAY)); + values.put(COLUMN_MONDAY, alarm.isRecurring(MONDAY)); + values.put(COLUMN_TUESDAY, alarm.isRecurring(TUESDAY)); + values.put(COLUMN_WEDNESDAY, alarm.isRecurring(WEDNESDAY)); + values.put(COLUMN_THURSDAY, alarm.isRecurring(THURSDAY)); + values.put(COLUMN_FRIDAY, alarm.isRecurring(FRIDAY)); + values.put(COLUMN_SATURDAY, alarm.isRecurring(SATURDAY)); + return values; + } + + public static class AlarmCursor extends CursorWrapper { + + private final Cursor mRecurrenceCursor; + + public AlarmCursor(Cursor alarmCursor, Cursor recurrenceCursor) { + super(alarmCursor); + mRecurrenceCursor = recurrenceCursor; + } + + /** + * @return an Alarm instance configured for the current row, + * or null if the current row is invalid + */ + public Alarm getAlarm() { + if (isBeforeFirst() || isAfterLast()) + return null; + Alarm alarm = Alarm.builder() + .hour(getInt(getColumnIndex(COLUMN_HOUR))) + .minutes(getInt(getColumnIndex(COLUMN_MINUTES))) + .vibrates(getInt(getColumnIndex(COLUMN_VIBRATES)) == 1) + .ringtone(getString(getColumnIndex(COLUMN_RINGTONE))) + .label(getString(getColumnIndex(COLUMN_LABEL))) + .build(); + alarm.setId(getLong(getColumnIndex(COLUMN_ID))); + alarm.setEnabled(getInt(getColumnIndex(COLUMN_ENABLED)) == 1); + alarm.setSnoozing(getLong(getColumnIndex(COLUMN_SNOOZING_UNTIL_MILLIS))); + alarm.setRecurring(SUNDAY, isRecurring(INDEX_SUNDAY)); + alarm.setRecurring(MONDAY, isRecurring(INDEX_MONDAY)); + alarm.setRecurring(TUESDAY, isRecurring(INDEX_TUESDAY)); + alarm.setRecurring(WEDNESDAY, isRecurring(INDEX_WEDNESDAY)); + alarm.setRecurring(THURSDAY, isRecurring(INDEX_THURSDAY)); + alarm.setRecurring(FRIDAY, isRecurring(INDEX_FRIDAY)); + alarm.setRecurring(SATURDAY, isRecurring(INDEX_SATURDAY)); + return alarm; + } + + private boolean isRecurring(int dayColumnIndex) { + return mRecurrenceCursor.getInt(dayColumnIndex) == 1; + } } } diff --git a/app/src/main/java/com/philliphsu/clock2/model/DatabaseManager.java b/app/src/main/java/com/philliphsu/clock2/model/DatabaseManager.java index 234b2c8..c7f6fc0 100644 --- a/app/src/main/java/com/philliphsu/clock2/model/DatabaseManager.java +++ b/app/src/main/java/com/philliphsu/clock2/model/DatabaseManager.java @@ -4,10 +4,10 @@ import android.content.Context; import com.philliphsu.clock2.Alarm; +import java.util.ArrayList; + /** * Created by Phillip Hsu on 6/25/2016. - * - * TODO: Not sure this class is all that useful? We could make the DbHelper singleton instance in its own class. */ public class DatabaseManager { @@ -27,8 +27,40 @@ public class DatabaseManager { return sDatabaseManager; } + // TODO: why return an Alarm? public Alarm insertAlarm(Alarm alarm) { mHelper.insertAlarm(alarm); return alarm; } + + public int updateAlarm(Alarm oldAlarm, Alarm newAlarm) { + return mHelper.updateAlarm(oldAlarm, newAlarm); + } + + public int deleteAlarm(Alarm alarm) { + return mHelper.deleteAlarm(alarm); + } + + // Since the query returns at most one row, just return the Alarm the row represents. + public Alarm getAlarm(long id) { + Alarm alarm = null; + AlarmDatabaseHelper.AlarmCursor cursor = mHelper.queryAlarm(id); + if (cursor != null && cursor.moveToFirst()) { + alarm = cursor.getAlarm(); + cursor.close(); + } + return alarm; + } + + public ArrayList getAlarms() { + ArrayList alarms = new ArrayList<>(); + AlarmDatabaseHelper.AlarmCursor cursor = mHelper.queryAlarms(); + if (cursor != null) { + while (cursor.moveToNext()) { + alarms.add(cursor.getAlarm()); + } + cursor.close(); + } + return alarms; + } }