CRUD operations implemented

This commit is contained in:
Phillip Hsu 2016-06-27 02:52:38 -07:00
parent 92b306afaa
commit 7747e4ee39
6 changed files with 243 additions and 75 deletions

View File

@ -70,6 +70,13 @@ public abstract class Alarm implements JsonSerializable {
return true;
}
/** <b>ONLY CALL THIS WHEN CREATING AN ALARM INSTANCE FROM A CURSOR</b> */
// 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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<Alarm> getAlarms() {
ArrayList<Alarm> alarms = new ArrayList<>();
AlarmDatabaseHelper.AlarmCursor cursor = mHelper.queryAlarms();
if (cursor != null) {
while (cursor.moveToNext()) {
alarms.add(cursor.getAlarm());
}
cursor.close();
}
return alarms;
}
}