diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java index baa95e137..4315c8081 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java @@ -80,7 +80,8 @@ public class ListHabitsController @NonNull ReminderScheduler reminderScheduler, @NonNull TaskRunner taskRunner, @NonNull WidgetUpdater widgetUpdater, - @NonNull ImportDataTaskFactory importTaskFactory, + @NonNull + ImportDataTaskFactory importTaskFactory, @NonNull ExportCSVTaskFactory exportCSVFactory) { this.adapter = adapter; @@ -155,6 +156,18 @@ public class ListHabitsController screen.showMessage(R.string.long_press_to_toggle); } + public void onRepairDB() + { + taskRunner.execute(() -> { + for(Habit h : habitList) { + h.getCheckmarks().invalidateNewerThan(0); + h.getStreaks().invalidateNewerThan(0); + h.getScores().invalidateNewerThan(0); + } + screen.showMessage(R.string.database_repaired); + }); + } + public void onSendBugReport() { try diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java index 295cb1678..3fb92e817 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java @@ -46,6 +46,8 @@ public class ListHabitsScreen extends BaseScreen public static final int RESULT_EXPORT_DB = 3; + public static final int RESULT_REPAIR_DB = 5; + public static final int RESULT_IMPORT_DATA = 1; @Nullable @@ -143,6 +145,10 @@ public class ListHabitsScreen extends BaseScreen case RESULT_BUG_REPORT: controller.onSendBugReport(); break; + + case RESULT_REPAIR_DB: + controller.onRepairDB(); + break; } } diff --git a/app/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java index 680f2720f..9b71bc153 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java @@ -57,6 +57,7 @@ public class SettingsFragment extends PreferenceFragmentCompat setResultOnPreferenceClick("importData", ListHabitsScreen.RESULT_IMPORT_DATA); setResultOnPreferenceClick("exportCSV", ListHabitsScreen.RESULT_EXPORT_CSV); setResultOnPreferenceClick("exportDB", ListHabitsScreen.RESULT_EXPORT_DB); + setResultOnPreferenceClick("repairDB", ListHabitsScreen.RESULT_REPAIR_DB); setResultOnPreferenceClick("bugReport", ListHabitsScreen.RESULT_BUG_REPORT); updateRingtoneDescription(); diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java index 2b23199e7..86488a8d1 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -130,6 +130,8 @@ public abstract class CheckmarkList */ public final int[] getValues(long from, long to) { + if(from > to) return new int[0]; + List checkmarks = getByInterval(from, to); int values[] = new int[checkmarks.size()]; diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/InconsistentDatabaseException.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/InconsistentDatabaseException.java new file mode 100644 index 000000000..2a3d862ca --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/InconsistentDatabaseException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +package org.isoron.uhabits.models.sqlite; + +public class InconsistentDatabaseException extends RuntimeException +{ + public InconsistentDatabaseException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java index ea42a846b..7c9b552e0 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java @@ -28,6 +28,7 @@ import com.activeandroid.query.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.records.*; +import org.isoron.uhabits.utils.*; import org.jetbrains.annotations.*; import java.util.*; @@ -99,6 +100,15 @@ public class SQLiteCheckmarkList extends CheckmarkList List records = sqlite.query(query, params); for (CheckmarkRecord record : records) record.habit = habitRecord; + + int nDays = DateUtils.getDaysBetween(fromTimestamp, toTimestamp) + 1; + if (records.size() != nDays) + { + throw new InconsistentDatabaseException( + String.format("habit=%s, %d expected, %d found", + habit.getName(), nDays, records.size())); + } + return toCheckmarks(records); } diff --git a/app/src/main/java/org/isoron/uhabits/notifications/NotificationTray.java b/app/src/main/java/org/isoron/uhabits/notifications/NotificationTray.java index 03fed27e5..d6a0253a2 100644 --- a/app/src/main/java/org/isoron/uhabits/notifications/NotificationTray.java +++ b/app/src/main/java/org/isoron/uhabits/notifications/NotificationTray.java @@ -21,6 +21,7 @@ package org.isoron.uhabits.notifications; import android.app.*; import android.content.*; +import android.graphics.*; import android.support.annotation.*; import android.support.v4.app.*; import android.support.v4.app.NotificationCompat.*; @@ -102,7 +103,7 @@ public class NotificationTray { DeleteHabitsCommand deleteCommand = (DeleteHabitsCommand) command; List deleted = deleteCommand.getHabits(); - for(Habit habit : deleted) + for (Habit habit : deleted) cancel(habit); } } @@ -191,9 +192,24 @@ public class NotificationTray if (!shouldShowReminderToday()) return; if (!habit.hasReminder()) return; - WearableExtender wearableExtender = - new WearableExtender().setBackground( - decodeResource(context.getResources(), R.drawable.stripe)); + Action checkAction = new Action(R.drawable.ic_action_check, + context.getString(R.string.check), + pendingIntents.addCheckmark(habit, timestamp)); + + Action snoozeAction = new Action(R.drawable.ic_action_snooze, + context.getString(R.string.snooze), + pendingIntents.snoozeNotification(habit)); + + Bitmap wearableBg = + decodeResource(context.getResources(), R.drawable.stripe); + + // Even though the set of actions is the same on the phone and + // on the watch, Pebble requires us to add them to the + // WearableExtender. + WearableExtender wearableExtender = new WearableExtender() + .setBackground(wearableBg) + .addAction(checkAction) + .addAction(snoozeAction); Notification notification = new NotificationCompat.Builder(context) .setSmallIcon(R.drawable.ic_notification) @@ -201,12 +217,8 @@ public class NotificationTray .setContentText(habit.getDescription()) .setContentIntent(pendingIntents.showHabit(habit)) .setDeleteIntent(pendingIntents.dismissNotification(habit)) - .addAction(R.drawable.ic_action_check, - context.getString(R.string.check), - pendingIntents.addCheckmark(habit, timestamp)) - .addAction(R.drawable.ic_action_snooze, - context.getString(R.string.snooze), - pendingIntents.snoozeNotification(habit)) + .addAction(checkAction) + .addAction(snoozeAction) .setSound(getRingtoneUri(context)) .extend(wearableExtender) .setWhen(reminderTime) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f1b013209..086e22859 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -189,4 +189,6 @@ Make notifications sticky Prevents notifications from being swiped away. + Repair database + Database repaired. \ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 16b874cc2..6b5502414 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -100,8 +100,11 @@ - + android:title="@string/generate_bug_report"/> + + diff --git a/app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java b/app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java index 910b251b5..dbb0df07e 100644 --- a/app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java +++ b/app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java @@ -139,9 +139,12 @@ public class DateUtilsTest extends BaseUnitTest public void test_getDaysBetween() { long t1 = timestamp(2016, JANUARY, 1); - long t2 = timestamp(2016, DECEMBER, 31); - int expected = 365; - assertThat(DateUtils.getDaysBetween(t1, t2), equalTo(expected)); - assertThat(DateUtils.getDaysBetween(t2, t1), equalTo(expected)); + long t2 = timestamp(2016, JANUARY, 10); + long t3 = timestamp(2016, DECEMBER, 31); + + assertThat(DateUtils.getDaysBetween(t1, t1), equalTo(0)); + assertThat(DateUtils.getDaysBetween(t1, t2), equalTo(9)); + assertThat(DateUtils.getDaysBetween(t1, t3), equalTo(365)); + assertThat(DateUtils.getDaysBetween(t3, t1), equalTo(365)); } }