From 605593d73992a9ef6d9c375fbb7cb77b9353087d Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Thu, 19 May 2016 12:51:36 -0400 Subject: [PATCH 001/184] Update UI tests --- app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index 89751372f..5dde5c695 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -341,6 +341,6 @@ public class MainTest { clickMenuItem(R.string.settings); clickSettingsItem("Generate bug report"); - intended(hasAction(Intent.ACTION_SENDTO)); + intended(hasAction(Intent.ACTION_SEND)); } } From 83ef8564e1329eae7ef115c4a8f715be516a77d2 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Thu, 26 May 2016 10:38:59 -0400 Subject: [PATCH 002/184] Reorganize file tree according to feature --- .../java/org/isoron/uhabits/BaseTest.java | 8 +- .../java/org/isoron/uhabits/ui/MainTest.java | 6 +- .../isoron/uhabits/unit/HabitFixtures.java | 23 ++-- .../commands/ToggleRepetitionCommandTest.java | 4 +- .../unit/io/HabitsCSVExporterTest.java | 4 +- .../isoron/uhabits/unit/io/ImportTest.java | 14 +-- .../unit/models/CheckmarkListTest.java | 12 +- .../isoron/uhabits/unit/models/HabitTest.java | 6 +- .../unit/models/RepetitionListTest.java | 28 ++--- .../uhabits/unit/models/ScoreListTest.java | 16 +-- .../unit/tasks/ImportDataTaskTest.java | 6 +- .../unit/views/CheckmarkWidgetViewTest.java | 12 +- .../unit/views/HabitHistoryViewTest.java | 6 +- .../uhabits/unit/views/NumberViewTest.java | 6 +- .../uhabits/unit/views/RingViewTest.java | 6 +- .../isoron/uhabits/unit/views/ViewTest.java | 10 +- app/src/main/AndroidManifest.xml | 12 +- .../uhabits/HabitBroadcastReceiver.java | 25 ++-- .../org/isoron/uhabits/HabitsApplication.java | 13 +- .../java/org/isoron/uhabits/MainActivity.java | 31 +++-- .../commands/ChangeHabitColorCommand.java | 6 +- .../uhabits/io/HabitBullCSVImporter.java | 4 +- .../isoron/uhabits/io/HabitsCSVExporter.java | 6 +- .../org/isoron/uhabits/io/LoopDBImporter.java | 9 +- .../isoron/uhabits/io/RewireDBImporter.java | 10 +- .../isoron/uhabits/io/TickmateDBImporter.java | 8 +- .../isoron/uhabits/models/CheckmarkList.java | 20 +-- .../java/org/isoron/uhabits/models/Habit.java | 12 +- .../isoron/uhabits/models/RepetitionList.java | 16 +-- .../org/isoron/uhabits/models/ScoreList.java | 24 ++-- .../org/isoron/uhabits/models/StreakList.java | 14 +-- .../isoron/uhabits/tasks/ExportCSVTask.java | 4 +- .../isoron/uhabits/tasks/ExportDBTask.java | 7 +- .../uhabits/{ => ui}/AboutActivity.java | 8 +- .../isoron/uhabits/{ => ui}/BaseActivity.java | 16 +-- .../uhabits/{helpers => ui}/HintManager.java | 7 +- .../uhabits/{ => ui}/IntroActivity.java | 4 +- .../edit}/EditHabitDialogFragment.java | 32 ++--- .../edit}/HistoryEditorDialog.java | 2 +- .../edit}/WeekdayPickerDialog.java | 6 +- .../list}/HabitSelectionCallback.java | 37 ++---- .../list/ListHabitsAdapter.java} | 14 +-- .../list}/ListHabitsFragment.java | 41 +++--- .../list}/ListHabitsHelper.java | 40 +++--- .../list/ListHabitsLoader.java} | 18 +-- .../settings}/FilePickerDialog.java | 2 +- .../{ => ui/settings}/SettingsActivity.java | 8 +- .../settings}/SettingsFragment.java | 14 +-- .../{ => ui/show}/ShowHabitActivity.java | 8 +- .../show}/ShowHabitFragment.java | 39 +++--- .../ColorUtils.java} | 6 +- .../DatabaseUtils.java} | 97 +------------- .../DateHelper.java => utils/DateUtils.java} | 6 +- .../org/isoron/uhabits/utils/FileUtils.java | 119 ++++++++++++++++++ .../InterfaceUtils.java} | 6 +- .../ReminderUtils.java} | 8 +- .../uhabits/views/CheckmarkWidgetView.java | 18 +-- .../uhabits/views/HabitFrequencyView.java | 34 ++--- .../uhabits/views/HabitHistoryView.java | 34 ++--- .../isoron/uhabits/views/HabitScoreView.java | 36 +++--- .../isoron/uhabits/views/HabitStreakView.java | 14 +-- .../isoron/uhabits/views/HabitWidgetView.java | 14 +-- .../org/isoron/uhabits/views/NumberView.java | 12 +- .../uhabits/views/RepetitionCountView.java | 18 +-- .../org/isoron/uhabits/views/RingView.java | 38 +++--- .../uhabits/widgets/BaseWidgetProvider.java | 10 +- .../uhabits/widgets/HabitPickerDialog.java | 1 - .../uhabits/widgets/ScoreWidgetProvider.java | 4 +- app/src/main/res/layout/edit_habit.xml | 2 +- .../main/res/layout/list_habits_activity.xml | 4 +- app/src/main/res/layout/settings_activity.xml | 4 +- app/src/main/res/layout/show_habit.xml | 2 +- .../main/res/layout/show_habit_activity.xml | 4 +- app/src/main/res/menu/list_habits_menu.xml | 2 +- app/src/main/res/menu/list_habits_options.xml | 2 +- 75 files changed, 607 insertions(+), 572 deletions(-) rename app/src/main/java/org/isoron/uhabits/{ => ui}/AboutActivity.java (92%) rename app/src/main/java/org/isoron/uhabits/{ => ui}/BaseActivity.java (91%) rename app/src/main/java/org/isoron/uhabits/{helpers => ui}/HintManager.java (90%) rename app/src/main/java/org/isoron/uhabits/{ => ui}/IntroActivity.java (96%) rename app/src/main/java/org/isoron/uhabits/{dialogs => ui/edit}/EditHabitDialogFragment.java (92%) rename app/src/main/java/org/isoron/uhabits/{dialogs => ui/edit}/HistoryEditorDialog.java (99%) rename app/src/main/java/org/isoron/uhabits/{dialogs => ui/edit}/WeekdayPickerDialog.java (93%) rename app/src/main/java/org/isoron/uhabits/{fragments => ui/list}/HabitSelectionCallback.java (86%) rename app/src/main/java/org/isoron/uhabits/{fragments/HabitListAdapter.java => ui/list/ListHabitsAdapter.java} (87%) rename app/src/main/java/org/isoron/uhabits/{fragments => ui/list}/ListHabitsFragment.java (92%) rename app/src/main/java/org/isoron/uhabits/{helpers => ui/list}/ListHabitsHelper.java (87%) rename app/src/main/java/org/isoron/uhabits/{loaders/HabitListLoader.java => ui/list/ListHabitsLoader.java} (92%) rename app/src/main/java/org/isoron/uhabits/{dialogs => ui/settings}/FilePickerDialog.java (99%) rename app/src/main/java/org/isoron/uhabits/{ => ui/settings}/SettingsActivity.java (82%) rename app/src/main/java/org/isoron/uhabits/{fragments => ui/settings}/SettingsFragment.java (90%) rename app/src/main/java/org/isoron/uhabits/{ => ui/show}/ShowHabitActivity.java (88%) rename app/src/main/java/org/isoron/uhabits/{fragments => ui/show}/ShowHabitFragment.java (89%) rename app/src/main/java/org/isoron/uhabits/{helpers/ColorHelper.java => utils/ColorUtils.java} (96%) rename app/src/main/java/org/isoron/uhabits/{helpers/DatabaseHelper.java => utils/DatabaseUtils.java} (56%) rename app/src/main/java/org/isoron/uhabits/{helpers/DateHelper.java => utils/DateUtils.java} (98%) create mode 100644 app/src/main/java/org/isoron/uhabits/utils/FileUtils.java rename app/src/main/java/org/isoron/uhabits/{helpers/UIHelper.java => utils/InterfaceUtils.java} (98%) rename app/src/main/java/org/isoron/uhabits/{helpers/ReminderHelper.java => utils/ReminderUtils.java} (96%) diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java index 5515b5121..2cf353d2f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java @@ -24,8 +24,8 @@ import android.os.Build; import android.os.Looper; import android.support.test.InstrumentationRegistry; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.tasks.BaseTask; import org.junit.Before; @@ -51,8 +51,8 @@ public class BaseTest targetContext = InstrumentationRegistry.getTargetContext(); testContext = InstrumentationRegistry.getContext(); - UIHelper.setFixedTheme(R.style.AppBaseTheme); - DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME); + InterfaceUtils.setFixedTheme(R.style.AppBaseTheme); + DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME); } protected void waitForAsyncTasks() throws InterruptedException, TimeoutException diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index 5dde5c695..e26fd2f17 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -29,10 +29,10 @@ import android.support.test.espresso.intent.rule.IntentsTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; -import org.isoron.uhabits.MainActivity; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.MainActivity; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -300,7 +300,7 @@ public class MainTest clickMenuItem(R.string.settings); - String date = DateHelper.getBackupDateFormat().format(DateHelper.getLocalTime()); + String date = DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); date = date.substring(0, date.length() - 2); clickSettingsItem("Export full backup"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java index 09298bb08..16c16cccf 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java @@ -23,8 +23,9 @@ import android.content.Context; import android.support.annotation.Nullable; import android.util.Log; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.tasks.ExportDBTask; @@ -50,11 +51,11 @@ public class HabitFixtures habit.freqDen = 3; habit.save(); - long timestamp = DateHelper.getStartOfToday(); + long timestamp = DateUtils.getStartOfToday(); for(boolean c : NON_DAILY_HABIT_CHECKS) { if(c) habit.repetitions.toggle(timestamp); - timestamp -= DateHelper.millisecondsInOneDay; + timestamp -= DateUtils.millisecondsInOneDay; } return habit; @@ -80,8 +81,8 @@ public class HabitFixtures habit.color = 4; habit.save(); - long day = DateHelper.millisecondsInOneDay; - long today = DateHelper.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; + long today = DateUtils.getStartOfToday(); int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, 28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120}; @@ -97,7 +98,7 @@ public class HabitFixtures final int nHabits = 30; final int nYears = 5; - DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command() + DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() { @Override public void execute() @@ -112,8 +113,8 @@ public class HabitFixtures habit.name = String.format("Habit %d", i); habit.save(); - long today = DateHelper.getStartOfToday(); - long day = DateHelper.millisecondsInOneDay; + long today = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; for(int j = 0; j < 365 * nYears; j++) @@ -147,12 +148,12 @@ public class HabitFixtures public static void loadHugeDataSet(Context testContext) throws Throwable { - File baseDir = DatabaseHelper.getFilesDir("Backups"); + File baseDir = FileUtils.getFilesDir("Backups"); if(baseDir == null) fail("baseDir should not be null"); File dst = new File(String.format("%s/%s", baseDir.getPath(), "loopHuge.db")); InputStream in = testContext.getAssets().open("fixtures/loopHuge.db"); - DatabaseHelper.copy(in, dst); + FileUtils.copy(in, dst); ImportDataTask task = new ImportDataTask(dst, null); task.execute(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java index 9d427d154..95993fbaa 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java @@ -24,7 +24,7 @@ import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseTest; import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; @@ -50,7 +50,7 @@ public class ToggleRepetitionCommandTest extends BaseTest habit = HabitFixtures.createShortHabit(); - today = DateHelper.getStartOfToday(); + today = DateUtils.getStartOfToday(); command = new ToggleRepetitionCommand(habit, today); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java index 2091d7aa3..395952075 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java @@ -25,7 +25,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -79,7 +79,7 @@ public class HabitsCSVExporterTest extends BaseTest File parent = outputFile.getParentFile(); if(parent != null) parent.mkdirs(); - DatabaseHelper.copy(stream, outputFile); + FileUtils.copy(stream, outputFile); } zip.close(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java index 8f6f3fbe0..2e1301bd2 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java @@ -25,8 +25,8 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.io.GenericImporter; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -58,18 +58,18 @@ public class ImportTest extends BaseTest public void setup() { super.setup(); - DateHelper.setFixedLocalTime(null); + DateUtils.setFixedLocalTime(null); HabitFixtures.purgeHabits(); context = InstrumentationRegistry.getInstrumentation().getContext(); - baseDir = DatabaseHelper.getFilesDir("Backups"); + baseDir = FileUtils.getFilesDir("Backups"); if(baseDir == null) fail("baseDir should not be null"); } private void copyAssetToFile(String assetPath, File dst) throws IOException { InputStream in = context.getAssets().open(assetPath); - DatabaseHelper.copy(in, dst); + FileUtils.copy(in, dst); } private void importFromFile(String assetFilename) throws IOException @@ -87,7 +87,7 @@ public class ImportTest extends BaseTest private boolean containsRepetition(Habit h, int year, int month, int day) { - GregorianCalendar date = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); date.set(year, month - 1, day); return h.repetitions.contains(date.getTimeInMillis()); } @@ -133,7 +133,7 @@ public class ImportTest extends BaseTest assertThat(habit.reminderHour, equalTo(8)); assertThat(habit.reminderMin, equalTo(0)); boolean[] reminderDays = {false, true, true, true, true, true, false}; - assertThat(habit.reminderDays, equalTo(DateHelper.packWeekdayList(reminderDays))); + assertThat(habit.reminderDays, equalTo(DateUtils.packWeekdayList(reminderDays))); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java index 5763892b8..98b05f0d7 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java @@ -23,7 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; import org.junit.After; @@ -60,7 +60,7 @@ public class CheckmarkListTest extends BaseTest @After public void tearDown() { - DateHelper.setFixedLocalTime(null); + DateUtils.setFixedLocalTime(null); } @Test @@ -121,8 +121,8 @@ public class CheckmarkListTest extends BaseTest @Test public void test_getValues_withValidInterval() { - long from = DateHelper.getStartOfToday() - 15 * DateHelper.millisecondsInOneDay; - long to = DateHelper.getStartOfToday() - 5 * DateHelper.millisecondsInOneDay; + long from = DateUtils.getStartOfToday() - 15 * DateUtils.millisecondsInOneDay; + long to = DateUtils.getStartOfToday() - 5 * DateUtils.millisecondsInOneDay; int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED, UNCHECKED, UNCHECKED, UNCHECKED, @@ -169,7 +169,7 @@ public class CheckmarkListTest extends BaseTest private void travelInTime(int days) { - DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME + - days * DateHelper.millisecondsInOneDay); + DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME + + days * DateUtils.millisecondsInOneDay); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java index f09664031..518b64004 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java @@ -24,7 +24,7 @@ import android.test.suitebuilder.annotation.SmallTest; import org.hamcrest.MatcherAssert; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; @@ -285,7 +285,7 @@ public class HabitTest extends BaseTest Habit habit = new Habit(); if(i % 2 == 0) { - habit.reminderDays = DateHelper.ALL_WEEK_DAYS; + habit.reminderDays = DateUtils.ALL_WEEK_DAYS; habit.reminderHour = 8; habit.reminderMin = 30; habitsWithReminder.add(habit); @@ -350,7 +350,7 @@ public class HabitTest extends BaseTest Habit h = new Habit(); assertThat(h.hasReminder(), is(false)); - h.reminderDays = DateHelper.ALL_WEEK_DAYS; + h.reminderDays = DateUtils.ALL_WEEK_DAYS; h.reminderHour = 8; h.reminderMin = 30; assertThat(h.hasReminder(), is(true)); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java index 2b152a25f..8acf02ab7 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java @@ -23,7 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Repetition; import org.isoron.uhabits.unit.HabitFixtures; @@ -62,31 +62,31 @@ public class RepetitionListTest extends BaseTest @After public void tearDown() { - DateHelper.setFixedLocalTime(null); + DateUtils.setFixedLocalTime(null); } @Test public void test_contains() { - long current = DateHelper.getStartOfToday(); + long current = DateUtils.getStartOfToday(); for(boolean b : HabitFixtures.NON_DAILY_HABIT_CHECKS) { assertThat(habit.repetitions.contains(current), equalTo(b)); - current -= DateHelper.millisecondsInOneDay; + current -= DateUtils.millisecondsInOneDay; } for(int i = 0; i < 3; i++) { assertThat(habit.repetitions.contains(current), equalTo(false)); - current -= DateHelper.millisecondsInOneDay; + current -= DateUtils.millisecondsInOneDay; } } @Test public void test_delete() { - long timestamp = DateHelper.getStartOfToday(); + long timestamp = DateUtils.getStartOfToday(); assertThat(habit.repetitions.contains(timestamp), equalTo(true)); habit.repetitions.delete(timestamp); @@ -96,7 +96,7 @@ public class RepetitionListTest extends BaseTest @Test public void test_toggle() { - long timestamp = DateHelper.getStartOfToday(); + long timestamp = DateUtils.getStartOfToday(); assertThat(habit.repetitions.contains(timestamp), equalTo(true)); habit.repetitions.toggle(timestamp); @@ -117,11 +117,11 @@ public class RepetitionListTest extends BaseTest for(Integer row[] : weekdayCount) Arrays.fill(row, 0); - GregorianCalendar day = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); // Sets the current date to the end of November day.set(2015, 10, 30); - DateHelper.setFixedLocalTime(day.getTimeInMillis()); + DateUtils.setFixedLocalTime(day.getTimeInMillis()); // Add repetitions randomly from January to December // Leaves the month of March empty, to check that it returns null @@ -168,19 +168,19 @@ public class RepetitionListTest extends BaseTest @Test public void test_count() { - long to = DateHelper.getStartOfToday(); - long from = to - 9 * DateHelper.millisecondsInOneDay; + long to = DateUtils.getStartOfToday(); + long from = to - 9 * DateUtils.millisecondsInOneDay; assertThat(habit.repetitions.count(from, to), equalTo(6)); - to = DateHelper.getStartOfToday() - DateHelper.millisecondsInOneDay; - from = to - 5 * DateHelper.millisecondsInOneDay; + to = DateUtils.getStartOfToday() - DateUtils.millisecondsInOneDay; + from = to - 5 * DateUtils.millisecondsInOneDay; assertThat(habit.repetitions.count(from, to), equalTo(3)); } @Test public void test_getOldest() { - long expectedOldestTimestamp = DateHelper.getStartOfToday() - 9 * DateHelper.millisecondsInOneDay; + long expectedOldestTimestamp = DateUtils.getStartOfToday() - 9 * DateUtils.millisecondsInOneDay; assertThat(habit.repetitions.getOldestTimestamp(), equalTo(expectedOldestTimestamp)); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java index 25e99cffc..017dacaec 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java @@ -23,8 +23,8 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; import org.isoron.uhabits.unit.HabitFixtures; @@ -57,7 +57,7 @@ public class ScoreListTest extends BaseTest @After public void tearDown() { - DateHelper.setFixedLocalTime(null); + DateUtils.setFixedLocalTime(null); } @Test @@ -104,11 +104,11 @@ public class ScoreListTest extends BaseTest 10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023, 4507040, 3699107, 2846927, 1948077, 1000000 }; - long current = DateHelper.getStartOfToday(); + long current = DateUtils.getStartOfToday(); for(int expectedValue : expectedValues) { assertThat(habit.scores.getValue(current), equalTo(expectedValue)); - current -= DateHelper.millisecondsInOneDay; + current -= DateUtils.millisecondsInOneDay; } } @@ -162,14 +162,14 @@ public class ScoreListTest extends BaseTest private void toggleRepetitions(final int from, final int to) { - DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command() + DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() { @Override public void execute() { - long today = DateHelper.getStartOfToday(); + long today = DateUtils.getStartOfToday(); for (int i = from; i < to; i++) - habit.repetitions.toggle(today - i * DateHelper.millisecondsInOneDay); + habit.repetitions.toggle(today - i * DateUtils.millisecondsInOneDay); } }); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java index d6a3cabaa..833675896 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java @@ -25,7 +25,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.widget.ProgressBar; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.tasks.ImportDataTask; import org.junit.Before; import org.junit.Test; @@ -50,14 +50,14 @@ public class ImportDataTaskTest extends BaseTest { super.setup(); - baseDir = DatabaseHelper.getFilesDir("Backups"); + baseDir = FileUtils.getFilesDir("Backups"); if(baseDir == null) fail("baseDir should not be null"); } private void copyAssetToFile(String assetPath, File dst) throws IOException { InputStream in = testContext.getAssets().open(assetPath); - DatabaseHelper.copy(in, dst); + FileUtils.copy(in, dst); } private void assertTaskResult(final int expectedResult, String assetFilename) throws Throwable diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java index 874c8666f..45c91242a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java @@ -23,8 +23,8 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; import org.isoron.uhabits.views.CheckmarkWidgetView; @@ -45,7 +45,7 @@ public class CheckmarkWidgetViewTest extends ViewTest public void setup() { super.setup(); - UIHelper.setFixedTheme(R.style.TransparentWidgetTheme); + InterfaceUtils.setFixedTheme(R.style.TransparentWidgetTheme); habit = HabitFixtures.createShortHabit(); view = new CheckmarkWidgetView(targetContext); @@ -63,7 +63,7 @@ public class CheckmarkWidgetViewTest extends ViewTest @Test public void testRender_unchecked() throws IOException { - habit.repetitions.toggle(DateHelper.getStartOfToday()); + habit.repetitions.toggle(DateUtils.getStartOfToday()); view.refreshData(); assertRenders(view, "CheckmarkView/unchecked.png"); @@ -72,8 +72,8 @@ public class CheckmarkWidgetViewTest extends ViewTest @Test public void testRender_implicitlyChecked() throws IOException { - long today = DateHelper.getStartOfToday(); - long day = DateHelper.millisecondsInOneDay; + long today = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; habit.repetitions.toggle(today); habit.repetitions.toggle(today - day); habit.repetitions.toggle(today - 2 * day); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java index ea8f0f54f..9982fd18a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; import org.isoron.uhabits.views.HabitHistoryView; @@ -92,7 +92,7 @@ public class HabitHistoryViewTest extends ViewTest tap(view, 340, 40); // today's square waitForAsyncTasks(); - long today = DateHelper.getStartOfToday(); + long today = DateUtils.getStartOfToday(); assertFalse(habit.repetitions.contains(today)); } @@ -118,7 +118,7 @@ public class HabitHistoryViewTest extends ViewTest tap(view, 340, 40); // today's square waitForAsyncTasks(); - long today = DateHelper.getStartOfToday(); + long today = DateUtils.getStartOfToday(); assertTrue(habit.repetitions.contains(today)); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java index a8f371af6..b4cdb0862 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java @@ -23,7 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; +import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.views.NumberView; import org.junit.Before; import org.junit.Test; @@ -45,7 +45,7 @@ public class NumberViewTest extends ViewTest view = new NumberView(targetContext); view.setLabel("Hello world"); view.setNumber(31); - view.setColor(ColorHelper.CSV_PALETTE[0]); + view.setColor(ColorUtils.CSV_PALETTE[0]); measureView(dpToPixels(100), dpToPixels(100), view); } @@ -68,7 +68,7 @@ public class NumberViewTest extends ViewTest public void testRender_withDifferentParams() throws IOException { view.setNumber(500); - view.setColor(ColorHelper.CSV_PALETTE[5]); + view.setColor(ColorUtils.CSV_PALETTE[5]); view.setTextSize(targetContext.getResources().getDimension(R.dimen.tinyTextSize)); measureView(dpToPixels(200), dpToPixels(200), view); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java index 9f831ba49..d59ee499e 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java @@ -23,7 +23,7 @@ import android.graphics.Color; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.helpers.ColorHelper; +import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.views.RingView; import org.junit.Before; import org.junit.Test; @@ -45,7 +45,7 @@ public class RingViewTest extends ViewTest view = new RingView(targetContext); view.setPercentage(0.6f); view.setText("60%"); - view.setColor(ColorHelper.CSV_PALETTE[0]); + view.setColor(ColorUtils.CSV_PALETTE[0]); view.setBackgroundColor(Color.WHITE); view.setThickness(dpToPixels(3)); } @@ -61,7 +61,7 @@ public class RingViewTest extends ViewTest public void testRender_withDifferentParams() throws IOException { view.setPercentage(0.25f); - view.setColor(ColorHelper.CSV_PALETTE[5]); + view.setColor(ColorUtils.CSV_PALETTE[5]); measureView(dpToPixels(200), dpToPixels(200), view); assertRenders(view, "RingView/renderDifferentParams.png"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index d87c3b9fc..8e9f527b3 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -27,8 +27,8 @@ import android.view.MotionEvent; import android.view.View; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.views.HabitDataView; @@ -124,8 +124,8 @@ public class ViewTest extends BaseTest private String saveBitmap(String filename, String suffix, Bitmap bitmap) throws IOException { - File dir = DatabaseHelper.getSDCardDir("test-screenshots"); - if(dir == null) dir = DatabaseHelper.getFilesDir("test-screenshots"); + File dir = FileUtils.getSDCardDir("test-screenshots"); + if(dir == null) dir = FileUtils.getFilesDir("test-screenshots"); if(dir == null) throw new RuntimeException("Could not find suitable dir for screenshots"); filename = filename.replaceAll("\\.png$", suffix + ".png"); @@ -191,7 +191,7 @@ public class ViewTest extends BaseTest protected int dpToPixels(int dp) { - return (int) UIHelper.dpToPixels(targetContext, dp); + return (int) InterfaceUtils.dpToPixels(targetContext, dp); } protected void tap(GestureDetector.OnGestureListener view, int x, int y) throws InterruptedException diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7eeedca29..4a764c2b5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,23 +60,23 @@ + android:value=".MainActivity"/> + android:value=".MainActivity"/> @@ -89,7 +89,7 @@ () { @Override protected Void doInBackground(Void... params) { - ReminderHelper.createReminderAlarms(MainActivity.this); + ReminderUtils.createReminderAlarms(MainActivity.this); updateWidgets(MainActivity.this); return null; } @@ -130,7 +135,7 @@ public class MainActivity extends BaseActivity { SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean("pref_first_run", false); - editor.putLong("last_hint_timestamp", DateHelper.getStartOfToday()).apply(); + editor.putLong("last_hint_timestamp", DateUtils.getStartOfToday()).apply(); editor.apply(); Intent intent = new Intent(this, IntroActivity.class); @@ -145,7 +150,7 @@ public class MainActivity extends BaseActivity getMenuInflater().inflate(R.menu.list_habits_menu, menu); MenuItem nightModeItem = menu.findItem(R.id.action_night_mode); - nightModeItem.setChecked(UIHelper.isNightMode()); + nightModeItem.setChecked(InterfaceUtils.isNightMode()); return true; } @@ -157,10 +162,10 @@ public class MainActivity extends BaseActivity { case R.id.action_night_mode: { - if(UIHelper.isNightMode()) - UIHelper.setCurrentTheme(UIHelper.THEME_LIGHT); + if(InterfaceUtils.isNightMode()) + InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_LIGHT); else - UIHelper.setCurrentTheme(UIHelper.THEME_DARK); + InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_DARK); refreshTheme(); return true; diff --git a/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java index 04ba83d7d..ffcd6dfee 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java @@ -19,10 +19,8 @@ package org.isoron.uhabits.commands; -import com.activeandroid.ActiveAndroid; - import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.models.Habit; import java.util.ArrayList; @@ -53,7 +51,7 @@ public class ChangeHabitColorCommand extends Command @Override public void undo() { - DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command() + DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() { @Override public void execute() diff --git a/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java b/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java index 46be626c9..5a1b6c514 100644 --- a/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java @@ -24,7 +24,7 @@ import android.support.annotation.NonNull; import com.activeandroid.ActiveAndroid; import com.opencsv.CSVReader; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import java.io.BufferedReader; @@ -76,7 +76,7 @@ public class HabitBullCSVImporter extends AbstractImporter int month = Integer.parseInt(dateString[1]); int day = Integer.parseInt(dateString[2]); - Calendar date = DateHelper.getStartOfTodayCalendar(); + Calendar date = DateUtils.getStartOfTodayCalendar(); date.set(year, month - 1, day); long timestamp = date.getTimeInMillis(); diff --git a/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java b/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java index f84ad666c..6ae20f8fc 100644 --- a/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java @@ -19,7 +19,7 @@ package org.isoron.uhabits.io; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.CheckmarkList; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.ScoreList; @@ -93,8 +93,8 @@ public class HabitsCSVExporter private String writeZipFile() throws IOException { - SimpleDateFormat dateFormat = DateHelper.getCSVDateFormat(); - String date = dateFormat.format(DateHelper.getStartOfToday()); + SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); + String date = dateFormat.format(DateUtils.getStartOfToday()); String zipFilename = String.format("%s/Loop Habits CSV %s.zip", exportDirName, date); FileOutputStream fos = new FileOutputStream(zipFilename); diff --git a/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java index 27b7ecb15..6050a384e 100644 --- a/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java @@ -25,7 +25,8 @@ import android.support.annotation.NonNull; import com.activeandroid.ActiveAndroid; -import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.FileUtils; import java.io.File; import java.io.IOException; @@ -54,8 +55,8 @@ public class LoopDBImporter extends AbstractImporter public void importHabitsFromFile(@NonNull File file) throws IOException { ActiveAndroid.dispose(); - File originalDB = DatabaseHelper.getDatabaseFile(); - DatabaseHelper.copy(file, originalDB); - DatabaseHelper.initializeActiveAndroid(); + File originalDB = DatabaseUtils.getDatabaseFile(); + FileUtils.copy(file, originalDB); + DatabaseUtils.initializeActiveAndroid(); } } diff --git a/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java index 47fc92020..a68d6c392 100644 --- a/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java @@ -23,8 +23,8 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import java.io.File; @@ -57,7 +57,7 @@ public class RewireDBImporter extends AbstractImporter final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null, SQLiteDatabase.OPEN_READONLY); - DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command() + DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() { @Override public void execute() @@ -150,7 +150,7 @@ public class RewireDBImporter extends AbstractImporter reminderDays[idx] = true; } - habit.reminderDays = DateHelper.packWeekdayList(reminderDays); + habit.reminderDays = DateUtils.packWeekdayList(reminderDays); habit.reminderHour = rewireReminder / 60; habit.reminderMin = rewireReminder % 60; habit.save(); @@ -178,7 +178,7 @@ public class RewireDBImporter extends AbstractImporter int month = Integer.parseInt(date.substring(4, 6)); int day = Integer.parseInt(date.substring(6, 8)); - GregorianCalendar cal = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); cal.set(year, month - 1, day); habit.repetitions.toggle(cal.getTimeInMillis()); diff --git a/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java index f0b6b9770..766af577a 100644 --- a/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java @@ -23,8 +23,8 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import java.io.File; @@ -57,7 +57,7 @@ public class TickmateDBImporter extends AbstractImporter final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null, SQLiteDatabase.OPEN_READONLY); - DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command() + DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() { @Override public void execute() @@ -118,7 +118,7 @@ public class TickmateDBImporter extends AbstractImporter int month = c.getInt(1); int day = c.getInt(2); - GregorianCalendar cal = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); cal.set(year, month, day); habit.repetitions.toggle(cal.getTimeInMillis()); 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 80b565c9c..da8e4ea77 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -29,8 +29,8 @@ import com.activeandroid.Cache; import com.activeandroid.query.Delete; import com.activeandroid.query.Select; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import java.io.IOException; import java.io.Writer; @@ -88,7 +88,7 @@ public class CheckmarkList Long.toString(toTimestamp) }; Cursor cursor = db.rawQuery(query, args); - long day = DateHelper.millisecondsInOneDay; + long day = DateUtils.millisecondsInOneDay; int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1; int[] checks = new int[nDays]; @@ -124,7 +124,7 @@ public class CheckmarkList if(oldestRep == null) return new int[0]; Long fromTimestamp = oldestRep.timestamp; - Long toTimestamp = DateHelper.getStartOfToday(); + Long toTimestamp = DateUtils.getStartOfToday(); return getValues(fromTimestamp, toTimestamp); } @@ -138,7 +138,7 @@ public class CheckmarkList long fromTimestamp = habit.repetitions.getOldestTimestamp(); if(fromTimestamp == 0) return; - Long toTimestamp = DateHelper.getStartOfToday(); + Long toTimestamp = DateUtils.getStartOfToday(); compute(fromTimestamp, toTimestamp); } @@ -152,9 +152,9 @@ public class CheckmarkList */ protected void compute(long from, final long to) { - UIHelper.throwIfMainThread(); + InterfaceUtils.throwIfMainThread(); - final long day = DateHelper.millisecondsInOneDay; + final long day = DateUtils.millisecondsInOneDay; Checkmark newestCheckmark = findNewest(); if(newestCheckmark != null) @@ -235,7 +235,7 @@ public class CheckmarkList { return new Select().from(Checkmark.class) .where("habit = ?", habit.getId()) - .and("timestamp <= ?", DateHelper.getStartOfToday()) + .and("timestamp <= ?", DateUtils.getStartOfToday()) .orderBy("timestamp desc") .limit(1) .executeSingle(); @@ -249,7 +249,7 @@ public class CheckmarkList @Nullable public Checkmark getToday() { - long today = DateHelper.getStartOfToday(); + long today = DateUtils.getStartOfToday(); compute(today, today); return findNewest(); } @@ -278,7 +278,7 @@ public class CheckmarkList { computeAll(); - SimpleDateFormat dateFormat = DateHelper.getCSVDateFormat(); + SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); String query = "select timestamp, value from checkmarks where habit = ? order by timestamp"; String params[] = { habit.getId().toString() }; diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 1fdb982ad..3751996cd 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -35,8 +35,8 @@ import com.activeandroid.query.Update; import com.activeandroid.util.SQLiteUtils; import com.opencsv.CSVWriter; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; import java.io.IOException; import java.io.Writer; @@ -154,7 +154,7 @@ public class Habit extends Model */ public Habit(Habit model) { - reminderDays = DateHelper.ALL_WEEK_DAYS; + reminderDays = DateUtils.ALL_WEEK_DAYS; copyAttributes(model); @@ -176,7 +176,7 @@ public class Habit extends Model this.archived = 0; this.freqDen = 7; this.freqNum = 3; - this.reminderDays = DateHelper.ALL_WEEK_DAYS; + this.reminderDays = DateUtils.ALL_WEEK_DAYS; checkmarks = new CheckmarkList(this); streaks = new StreakList(this); @@ -475,7 +475,7 @@ public class Habit extends Model { reminderHour = null; reminderMin = null; - reminderDays = DateHelper.ALL_WEEK_DAYS; + reminderDays = DateUtils.ALL_WEEK_DAYS; } /** @@ -502,7 +502,7 @@ public class Habit extends Model habit.description, Integer.toString(habit.freqNum), Integer.toString(habit.freqDen), - ColorHelper.toHTML(ColorHelper.CSV_PALETTE[habit.color]) + ColorUtils.toHTML(ColorUtils.CSV_PALETTE[habit.color]) }; csv.writeNext(cols, false); diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index 5bfe22fba..1e4227e9f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -30,8 +30,8 @@ import com.activeandroid.query.From; import com.activeandroid.query.Select; import com.activeandroid.util.SQLiteUtils; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.DateUtils; import java.util.Arrays; import java.util.GregorianCalendar; @@ -52,7 +52,7 @@ public class RepetitionList { return new Select().from(Repetition.class) .where("habit = ?", habit.getId()) - .and("timestamp <= ?", DateHelper.getStartOfToday()) + .and("timestamp <= ?", DateUtils.getStartOfToday()) .orderBy("timestamp"); } @@ -95,7 +95,7 @@ public class RepetitionList */ public void toggle(long timestamp) { - timestamp = DateHelper.getStartOfDay(timestamp); + timestamp = DateUtils.getStartOfDay(timestamp); if (contains(timestamp)) delete(timestamp); @@ -133,11 +133,11 @@ public class RepetitionList */ public long getOldestTimestamp() { - String[] args = { habit.getId().toString(), Long.toString(DateHelper.getStartOfToday()) }; + String[] args = { habit.getId().toString(), Long.toString(DateUtils.getStartOfToday()) }; String query = "select timestamp from Repetitions where habit = ? and timestamp <= ? " + "order by timestamp limit 1"; - return DatabaseHelper.longQuery(query, args); + return DatabaseUtils.longQuery(query, args); } /** @@ -164,7 +164,7 @@ public class RepetitionList "group by year, month, weekday"; String[] params = { habit.getId().toString(), - Long.toString(DateHelper.getStartOfToday()) }; + Long.toString(DateUtils.getStartOfToday()) }; SQLiteDatabase db = Cache.openDatabase(); Cursor cursor = db.rawQuery(query, params); @@ -172,7 +172,7 @@ public class RepetitionList if(!cursor.moveToFirst()) return new HashMap<>(); HashMap map = new HashMap<>(); - GregorianCalendar date = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); do { diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index eca89b9e0..b207b130c 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -31,9 +31,9 @@ import com.activeandroid.query.From; import com.activeandroid.query.Select; import com.activeandroid.util.SQLiteUtils; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import java.io.IOException; import java.io.Writer; @@ -85,7 +85,7 @@ public class ScoreList long fromTimestamp = habit.repetitions.getOldestTimestamp(); if(fromTimestamp == 0) return; - long toTimestamp = DateHelper.getStartOfToday(); + long toTimestamp = DateUtils.getStartOfToday(); compute(fromTimestamp, toTimestamp); } @@ -103,9 +103,9 @@ public class ScoreList */ protected void compute(long from, long to) { - UIHelper.throwIfMainThread(); + InterfaceUtils.throwIfMainThread(); - final long day = DateHelper.millisecondsInOneDay; + final long day = DateUtils.millisecondsInOneDay; final double freq = ((double) habit.freqNum) / habit.freqDen; int newestScoreValue = findNewestValue(); @@ -151,7 +151,7 @@ public class ScoreList { String args[] = { habit.getId().toString() }; String query = "select timestamp from Score where habit = ? order by timestamp desc limit 1"; - return DatabaseHelper.longQuery(query, args); + return DatabaseUtils.longQuery(query, args); } private void insert(long timestamps[], long values[]) @@ -238,7 +238,7 @@ public class ScoreList if(oldestRep == null) return new int[0]; long fromTimestamp = oldestRep.timestamp; - long toTimestamp = DateHelper.getStartOfToday(); + long toTimestamp = DateUtils.getStartOfToday(); return getValues(fromTimestamp, toTimestamp, divisor); } @@ -255,7 +255,7 @@ public class ScoreList { compute(from, to); - divisor *= DateHelper.millisecondsInOneDay; + divisor *= DateUtils.millisecondsInOneDay; Long offset = to + divisor; String query = "select ((timestamp - ?) / ?) as time, avg(score) from Score " + @@ -291,7 +291,7 @@ public class ScoreList @Nullable protected Score getToday() { - return get(DateHelper.getStartOfToday()); + return get(DateUtils.getStartOfToday()); } /** @@ -301,7 +301,7 @@ public class ScoreList */ public int getTodayValue() { - return getValue(DateHelper.getStartOfToday()); + return getValue(DateUtils.getStartOfToday()); } /** @@ -321,7 +321,7 @@ public class ScoreList { computeAll(); - SimpleDateFormat dateFormat = DateHelper.getCSVDateFormat(); + SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); String query = "select timestamp, score from score where habit = ? order by timestamp"; String params[] = { habit.getId().toString() }; diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java index 691403d25..a47409ef1 100644 --- a/app/src/main/java/org/isoron/uhabits/models/StreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java @@ -27,8 +27,8 @@ import com.activeandroid.Cache; import com.activeandroid.query.Delete; import com.activeandroid.query.Select; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import java.util.ArrayList; import java.util.LinkedList; @@ -50,7 +50,7 @@ public class StreakList String query = "select * from (select * from streak where habit=? " + "order by end <> ?, length desc, end desc limit ?) order by end desc"; - String params[] = {habit.getId().toString(), Long.toString(DateHelper.getStartOfToday()), + String params[] = {habit.getId().toString(), Long.toString(DateUtils.getStartOfToday()), Integer.toString(limit)}; SQLiteDatabase db = Cache.openDatabase(); @@ -87,11 +87,11 @@ public class StreakList public void rebuild() { - UIHelper.throwIfMainThread(); + InterfaceUtils.throwIfMainThread(); long beginning; - long today = DateHelper.getStartOfToday(); - long day = DateHelper.millisecondsInOneDay; + long today = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; Streak newestStreak = getNewest(); if (newestStreak != null) @@ -154,7 +154,7 @@ public class StreakList { new Delete().from(Streak.class) .where("habit = ?", habit.getId()) - .and("end >= ?", timestamp - DateHelper.millisecondsInOneDay) + .and("end >= ?", timestamp - DateUtils.millisecondsInOneDay) .execute(); } } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java index 5c85bc89e..ccea2b14b 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java @@ -23,7 +23,7 @@ import android.support.annotation.Nullable; import android.view.View; import android.widget.ProgressBar; -import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; @@ -83,7 +83,7 @@ public class ExportCSVTask extends BaseTask { try { - File dir = DatabaseHelper.getFilesDir("CSV"); + File dir = FileUtils.getFilesDir("CSV"); if(dir == null) return; HabitsCSVExporter exporter = new HabitsCSVExporter(selectedHabits, dir); diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java index 4e184335f..60b3cba19 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java @@ -23,7 +23,8 @@ import android.support.annotation.Nullable; import android.view.View; import android.widget.ProgressBar; -import org.isoron.uhabits.helpers.DatabaseHelper; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.FileUtils; import java.io.File; import java.io.IOException; @@ -80,10 +81,10 @@ public class ExportDBTask extends BaseTask try { - File dir = DatabaseHelper.getFilesDir("Backups"); + File dir = FileUtils.getFilesDir("Backups"); if(dir == null) return; - filename = DatabaseHelper.saveDatabaseCopy(dir); + filename = DatabaseUtils.saveDatabaseCopy(dir); } catch(IOException e) { diff --git a/app/src/main/java/org/isoron/uhabits/AboutActivity.java b/app/src/main/java/org/isoron/uhabits/ui/AboutActivity.java similarity index 92% rename from app/src/main/java/org/isoron/uhabits/AboutActivity.java rename to app/src/main/java/org/isoron/uhabits/ui/AboutActivity.java index 279a96d45..a4a575a7f 100644 --- a/app/src/main/java/org/isoron/uhabits/AboutActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/AboutActivity.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits; +package org.isoron.uhabits.ui; import android.content.Intent; import android.net.Uri; @@ -25,7 +25,9 @@ import android.os.Bundle; import android.view.View; import android.widget.TextView; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.BuildConfig; +import org.isoron.uhabits.R; +import org.isoron.uhabits.utils.InterfaceUtils; public class AboutActivity extends BaseActivity implements View.OnClickListener { @@ -38,7 +40,7 @@ public class AboutActivity extends BaseActivity implements View.OnClickListener setContentView(R.layout.about); setupSupportActionBar(true); - int color = UIHelper.getStyledColor(this, R.attr.aboutScreenColor); + int color = InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); setupActionBarColor(color); TextView tvVersion = (TextView) findViewById(R.id.tvVersion); diff --git a/app/src/main/java/org/isoron/uhabits/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java similarity index 91% rename from app/src/main/java/org/isoron/uhabits/BaseActivity.java rename to app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java index 6d59a00d0..f41b6eb87 100644 --- a/app/src/main/java/org/isoron/uhabits/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits; +package org.isoron.uhabits.ui; import android.app.backup.BackupManager; import android.graphics.Color; @@ -31,9 +31,11 @@ import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Toast; +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import java.util.LinkedList; @@ -52,7 +54,7 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U { super.onCreate(savedInstanceState); - UIHelper.applyCurrentTheme(this); + InterfaceUtils.applyCurrentTheme(this); androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); @@ -133,7 +135,7 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U if(toolbar == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - toolbar.setElevation(UIHelper.dpToPixels(this, 2)); + toolbar.setElevation(InterfaceUtils.dpToPixels(this, 2)); setSupportActionBar(toolbar); @@ -172,14 +174,14 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U ActionBar actionBar = getSupportActionBar(); if(actionBar == null) return; - if (!UIHelper.getStyledBoolean(this, R.attr.useHabitColorAsPrimary)) return; + if (!InterfaceUtils.getStyledBoolean(this, R.attr.useHabitColorAsPrimary)) return; ColorDrawable drawable = new ColorDrawable(color); actionBar.setBackgroundDrawable(drawable); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - int darkerColor = ColorHelper.mixColors(color, Color.BLACK, 0.75f); + int darkerColor = ColorUtils.mixColors(color, Color.BLACK, 0.75f); getWindow().setStatusBarColor(darkerColor); } } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/HintManager.java b/app/src/main/java/org/isoron/uhabits/ui/HintManager.java similarity index 90% rename from app/src/main/java/org/isoron/uhabits/helpers/HintManager.java rename to app/src/main/java/org/isoron/uhabits/ui/HintManager.java index 998939ed9..aa366833f 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/HintManager.java +++ b/app/src/main/java/org/isoron/uhabits/ui/HintManager.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.helpers; +package org.isoron.uhabits.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -28,6 +28,7 @@ import android.view.View; import android.widget.TextView; import org.isoron.uhabits.R; +import org.isoron.uhabits.utils.DateUtils; public class HintManager { @@ -59,7 +60,7 @@ public class HintManager Integer lastHintNumber = prefs.getInt("last_hint_number", -1); Long lastHintTimestamp = prefs.getLong("last_hint_timestamp", -1); - if (DateHelper.getStartOfToday() > lastHintTimestamp) showHint(lastHintNumber + 1); + if (DateUtils.getStartOfToday() > lastHintTimestamp) showHint(lastHintNumber + 1); } private void showHint(int hintNumber) @@ -68,7 +69,7 @@ public class HintManager if (hintNumber >= hints.length) return; prefs.edit().putInt("last_hint_number", hintNumber).apply(); - prefs.edit().putLong("last_hint_timestamp", DateHelper.getStartOfToday()).apply(); + prefs.edit().putLong("last_hint_timestamp", DateUtils.getStartOfToday()).apply(); TextView tvContent = (TextView) hintView.findViewById(R.id.hintContent); tvContent.setText(hints[hintNumber]); diff --git a/app/src/main/java/org/isoron/uhabits/IntroActivity.java b/app/src/main/java/org/isoron/uhabits/ui/IntroActivity.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/IntroActivity.java rename to app/src/main/java/org/isoron/uhabits/ui/IntroActivity.java index e298d1c63..f58298186 100644 --- a/app/src/main/java/org/isoron/uhabits/IntroActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/IntroActivity.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits; +package org.isoron.uhabits.ui; import android.graphics.Color; import android.os.Bundle; @@ -25,6 +25,8 @@ import android.os.Bundle; import com.github.paolorotolo.appintro.AppIntro2; import com.github.paolorotolo.appintro.AppIntroFragment; +import org.isoron.uhabits.R; + public class IntroActivity extends AppIntro2 { @Override diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/edit/EditHabitDialogFragment.java similarity index 92% rename from app/src/main/java/org/isoron/uhabits/dialogs/EditHabitDialogFragment.java rename to app/src/main/java/org/isoron/uhabits/ui/edit/EditHabitDialogFragment.java index 1baa97cbe..1a21a8c9c 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/edit/EditHabitDialogFragment.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.dialogs; +package org.isoron.uhabits.ui.edit; import android.annotation.SuppressLint; import android.content.SharedPreferences; @@ -44,10 +44,10 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.commands.EditHabitCommand; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper.OnSavedListener; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import java.util.Arrays; @@ -59,7 +59,7 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment static final int EDIT_MODE = 0; static final int CREATE_MODE = 1; - private OnSavedListener onSavedListener; + private InterfaceUtils.OnSavedListener onSavedListener; private Habit originalHabit; private Habit modifiedHabit; @@ -176,7 +176,7 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment private void changeColor(int paletteColor) { modifiedHabit.color = paletteColor; - tvName.setTextColor(ColorHelper.getColor(getActivity(), paletteColor)); + tvName.setTextColor(ColorUtils.getColor(getActivity(), paletteColor)); SharedPreferences.Editor editor = prefs.edit(); editor.putInt("pref_default_habit_palette_color", paletteColor); @@ -188,12 +188,12 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment { if (modifiedHabit.hasReminder()) { - tvReminderTime.setText(DateHelper.formatTime(getActivity(), modifiedHabit.reminderHour, + tvReminderTime.setText(DateUtils.formatTime(getActivity(), modifiedHabit.reminderHour, modifiedHabit.reminderMin)); llReminderDays.setVisibility(View.VISIBLE); - boolean weekdays[] = DateHelper.unpackWeekdayList(modifiedHabit.reminderDays); - tvReminderDays.setText(DateHelper.formatWeekdayList(getActivity(), weekdays)); + boolean weekdays[] = DateUtils.unpackWeekdayList(modifiedHabit.reminderDays); + tvReminderDays.setText(DateUtils.formatWeekdayList(getActivity(), weekdays)); } else { @@ -202,7 +202,7 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment } } - public void setOnSavedListener(OnSavedListener onSavedListener) + public void setOnSavedListener(InterfaceUtils.OnSavedListener onSavedListener) { this.onSavedListener = onSavedListener; } @@ -236,17 +236,17 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment private void onColorButtonClick() { - int originalAndroidColor = ColorHelper.getColor(getActivity(), modifiedHabit.color); + int originalAndroidColor = ColorUtils.getColor(getActivity(), modifiedHabit.color); ColorPickerDialog picker = ColorPickerDialog.newInstance( - R.string.color_picker_default_title, ColorHelper.getPalette(getActivity()), + R.string.color_picker_default_title, ColorUtils.getPalette(getActivity()), originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL); picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() { public void onColorSelected(int androidColor) { - int paletteColor = ColorHelper.colorToPaletteIndex(getActivity(), androidColor); + int paletteColor = ColorUtils.colorToPaletteIndex(getActivity(), androidColor); changeColor(paletteColor); } }); @@ -331,7 +331,7 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment WeekdayPickerDialog dialog = new WeekdayPickerDialog(); dialog.setListener(this); - dialog.setSelectedDays(DateHelper.unpackWeekdayList(modifiedHabit.reminderDays)); + dialog.setSelectedDays(DateUtils.unpackWeekdayList(modifiedHabit.reminderDays)); dialog.show(getFragmentManager(), "weekdayPicker"); } @@ -340,7 +340,7 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment { modifiedHabit.reminderHour = hour; modifiedHabit.reminderMin = minute; - modifiedHabit.reminderDays = DateHelper.ALL_WEEK_DAYS; + modifiedHabit.reminderDays = DateUtils.ALL_WEEK_DAYS; updateReminder(); } @@ -360,7 +360,7 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment if(count == 0) Arrays.fill(selectedDays, true); - modifiedHabit.reminderDays = DateHelper.packWeekdayList(selectedDays); + modifiedHabit.reminderDays = DateUtils.packWeekdayList(selectedDays); updateReminder(); } diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/ui/edit/HistoryEditorDialog.java similarity index 99% rename from app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java rename to app/src/main/java/org/isoron/uhabits/ui/edit/HistoryEditorDialog.java index cd95e9212..d43bcfcbf 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/edit/HistoryEditorDialog.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.dialogs; +package org.isoron.uhabits.ui.edit; import android.app.Dialog; import android.content.Context; diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java b/app/src/main/java/org/isoron/uhabits/ui/edit/WeekdayPickerDialog.java similarity index 93% rename from app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java rename to app/src/main/java/org/isoron/uhabits/ui/edit/WeekdayPickerDialog.java index 5297b80eb..e2de134d1 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/WeekdayPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/edit/WeekdayPickerDialog.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.dialogs; +package org.isoron.uhabits.ui.edit; import android.app.Dialog; import android.content.DialogInterface; @@ -26,7 +26,7 @@ import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatDialogFragment; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; public class WeekdayPickerDialog extends AppCompatDialogFragment implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener @@ -55,7 +55,7 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(R.string.select_weekdays) - .setMultiChoiceItems(DateHelper.getLongDayNames(), selectedDays, this) + .setMultiChoiceItems(DateUtils.getLongDayNames(), selectedDays, this) .setPositiveButton(android.R.string.yes, this) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { diff --git a/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/ui/list/HabitSelectionCallback.java similarity index 86% rename from app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java rename to app/src/main/java/org/isoron/uhabits/ui/list/HabitSelectionCallback.java index bf0973aaf..a065054b7 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/ui/list/HabitSelectionCallback.java @@ -17,48 +17,45 @@ * with this program. If not, see . */ -package org.isoron.uhabits.fragments; +package org.isoron.uhabits.ui.list; import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.support.v7.view.ActionMode; import android.view.Menu; import android.view.MenuItem; -import android.widget.ProgressBar; import com.android.colorpicker.ColorPickerDialog; import com.android.colorpicker.ColorPickerSwatch; -import org.isoron.uhabits.BaseActivity; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.ArchiveHabitsCommand; import org.isoron.uhabits.commands.ChangeHabitColorCommand; import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; -import org.isoron.uhabits.dialogs.EditHabitDialogFragment; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.UIHelper; -import org.isoron.uhabits.loaders.HabitListLoader; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.edit.EditHabitDialogFragment; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import java.util.LinkedList; import java.util.List; public class HabitSelectionCallback implements ActionMode.Callback { - private HabitListLoader loader; + private ListHabitsLoader loader; private List selectedPositions; private BaseActivity activity; private Listener listener; - private UIHelper.OnSavedListener onSavedListener; - private ProgressBar progressBar; + private InterfaceUtils.OnSavedListener onSavedListener; public interface Listener { void onActionModeDestroyed(ActionMode mode); } - public HabitSelectionCallback(BaseActivity activity, HabitListLoader loader) + public HabitSelectionCallback(BaseActivity activity, ListHabitsLoader loader) { this.activity = activity; this.loader = loader; @@ -70,12 +67,7 @@ public class HabitSelectionCallback implements ActionMode.Callback this.listener = listener; } - public void setProgressBar(ProgressBar progressBar) - { - this.progressBar = progressBar; - } - - public void setOnSavedListener(UIHelper.OnSavedListener onSavedListener) + public void setOnSavedListener(InterfaceUtils.OnSavedListener onSavedListener) { this.onSavedListener = onSavedListener; } @@ -110,10 +102,7 @@ public class HabitSelectionCallback implements ActionMode.Callback for (int i : selectedPositions) { Habit h = loader.habitsList.get(i); - if (h.isArchived()) - { - showArchive = false; - } + if (h.isArchived()) showArchive = false; else showUnarchive = false; } @@ -165,17 +154,17 @@ public class HabitSelectionCallback implements ActionMode.Callback case R.id.action_color: { - int originalAndroidColor = ColorHelper.getColor(activity, firstHabit.color); + int originalAndroidColor = ColorUtils.getColor(activity, firstHabit.color); ColorPickerDialog picker = ColorPickerDialog.newInstance( - R.string.color_picker_default_title, ColorHelper.getPalette(activity), + R.string.color_picker_default_title, ColorUtils.getPalette(activity), originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL); picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() { public void onColorSelected(int androidColor) { - int paletteColor = ColorHelper.colorToPaletteIndex(activity, + int paletteColor = ColorUtils.colorToPaletteIndex(activity, androidColor); activity.executeCommand(new ChangeHabitColorCommand(selectedHabits, paletteColor), null); diff --git a/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsAdapter.java similarity index 87% rename from app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java rename to app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsAdapter.java index e01ceef84..dd0bf2810 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/HabitListAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsAdapter.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.fragments; +package org.isoron.uhabits.ui.list; import android.content.Context; import android.view.LayoutInflater; @@ -26,23 +26,21 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.ListHabitsHelper; -import org.isoron.uhabits.loaders.HabitListLoader; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import java.util.List; -class HabitListAdapter extends BaseAdapter +class ListHabitsAdapter extends BaseAdapter { private LayoutInflater inflater; - private HabitListLoader loader; + private ListHabitsLoader loader; private ListHabitsHelper helper; private List selectedPositions; private View.OnLongClickListener onCheckmarkLongClickListener; private View.OnClickListener onCheckmarkClickListener; - public HabitListAdapter(Context context, HabitListLoader loader) + public ListHabitsAdapter(Context context, ListHabitsLoader loader) { this.loader = loader; @@ -74,7 +72,7 @@ class HabitListAdapter extends BaseAdapter final Habit habit = loader.habitsList.get(position); boolean selected = selectedPositions.contains(position); - if (view == null || (Long) view.getTag(R.id.timestamp_key) != DateHelper.getStartOfToday()) + if (view == null || (Long) view.getTag(R.id.timestamp_key) != DateUtils.getStartOfToday()) { view = helper.inflateHabitCard(inflater, onCheckmarkLongClickListener, onCheckmarkClickListener); diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java similarity index 92% rename from app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java rename to app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java index 1319a69b1..4b9bf23d1 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.fragments; +package org.isoron.uhabits.ui.list; import android.app.Activity; import android.content.Intent; @@ -50,20 +50,18 @@ import com.mobeta.android.dslv.DragSortController; import com.mobeta.android.dslv.DragSortListView; import com.mobeta.android.dslv.DragSortListView.DropListener; -import org.isoron.uhabits.BaseActivity; +import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.dialogs.EditHabitDialogFragment; -import org.isoron.uhabits.dialogs.FilePickerDialog; -import org.isoron.uhabits.helpers.DatabaseHelper; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.HintManager; -import org.isoron.uhabits.helpers.ListHabitsHelper; -import org.isoron.uhabits.helpers.ReminderHelper; -import org.isoron.uhabits.helpers.UIHelper; -import org.isoron.uhabits.helpers.UIHelper.OnSavedListener; -import org.isoron.uhabits.loaders.HabitListLoader; +import org.isoron.uhabits.ui.edit.EditHabitDialogFragment; +import org.isoron.uhabits.ui.settings.FilePickerDialog; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.ui.HintManager; +import org.isoron.uhabits.utils.ReminderUtils; +import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.utils.InterfaceUtils.OnSavedListener; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.ExportCSVTask; import org.isoron.uhabits.tasks.ExportDBTask; @@ -76,7 +74,7 @@ import java.util.List; public class ListHabitsFragment extends Fragment implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener, - OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener, + OnClickListener, ListHabitsLoader.Listener, AdapterView.OnItemLongClickListener, HabitSelectionCallback.Listener, ImportDataTask.Listener, ExportCSVTask.Listener, ExportDBTask.Listener { @@ -85,8 +83,8 @@ public class ListHabitsFragment extends Fragment private boolean showArchived; private ActionMode actionMode; - private HabitListAdapter adapter; - private HabitListLoader loader; + private ListHabitsAdapter adapter; + private ListHabitsLoader loader; private HintManager hintManager; private ListHabitsHelper helper; private List selectedPositions; @@ -114,7 +112,7 @@ public class ListHabitsFragment extends Fragment progressBar.setVisibility(View.GONE); selectedPositions = new LinkedList<>(); - loader = new HabitListLoader(); + loader = new ListHabitsLoader(); helper = new ListHabitsHelper(activity, loader); hintManager = new HintManager(activity, llHint); @@ -122,9 +120,9 @@ public class ListHabitsFragment extends Fragment loader.setCheckmarkCount(helper.getButtonCount()); llHint.setOnClickListener(this); - tvStarEmpty.setTypeface(UIHelper.getFontAwesome(activity)); + tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(activity)); - adapter = new HabitListAdapter(getActivity(), loader); + adapter = new ListHabitsAdapter(getActivity(), loader); adapter.setSelectedPositions(selectedPositions); adapter.setOnCheckmarkClickListener(this); adapter.setOnCheckmarkLongClickListener(this); @@ -171,7 +169,7 @@ public class ListHabitsFragment extends Fragment super.onResume(); Long timestamp = loader.getLastLoadTimestamp(); - if (timestamp != null && timestamp != DateHelper.getStartOfToday()) + if (timestamp != null && timestamp != DateUtils.getStartOfToday()) loader.updateAllHabits(true); helper.updateEmptyMessage(llEmpty); @@ -282,7 +280,6 @@ public class ListHabitsFragment extends Fragment { HabitSelectionCallback callback = new HabitSelectionCallback(activity, loader); callback.setSelectedPositions(selectedPositions); - callback.setProgressBar(progressBar); callback.setOnSavedListener(this); callback.setListener(this); @@ -301,7 +298,7 @@ public class ListHabitsFragment extends Fragment else activity.executeCommand(command, h.getId()); adapter.notifyDataSetChanged(); - ReminderHelper.createReminderAlarms(activity); + ReminderUtils.createReminderAlarms(activity); if(actionMode != null) actionMode.finish(); } @@ -433,7 +430,7 @@ public class ListHabitsFragment extends Fragment public void showImportDialog() { - File dir = DatabaseHelper.getFilesDir(null); + File dir = FileUtils.getFilesDir(null); if(dir == null) { activity.showToast(R.string.could_not_import); diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsHelper.java similarity index 87% rename from app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java rename to app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsHelper.java index 91a3a4aff..71411f383 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/ListHabitsHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsHelper.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.helpers; +package org.isoron.uhabits.ui.list; import android.content.Context; import android.content.SharedPreferences; @@ -32,7 +32,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.isoron.uhabits.R; -import org.isoron.uhabits.loaders.HabitListLoader; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; import org.isoron.uhabits.views.RingView; @@ -48,20 +50,20 @@ public class ListHabitsHelper private final int mediumContrastColor; private final Context context; - private final HabitListLoader loader; + private final ListHabitsLoader loader; - public ListHabitsHelper(Context context, HabitListLoader loader) + public ListHabitsHelper(Context context, ListHabitsLoader loader) { this.context = context; this.loader = loader; - lowContrastColor = UIHelper.getStyledColor(context, R.attr.lowContrastTextColor); - mediumContrastColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor); + lowContrastColor = InterfaceUtils.getStyledColor(context, R.attr.lowContrastTextColor); + mediumContrastColor = InterfaceUtils.getStyledColor(context, R.attr.mediumContrastTextColor); } public int getButtonCount() { - float screenWidth = UIHelper.getScreenWidth(context); + float screenWidth = InterfaceUtils.getScreenWidth(context); float labelWidth = context.getResources().getDimension(R.dimen.habitNameWidth); float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth); return Math.max(0, (int) ((screenWidth - labelWidth) / buttonWidth)); @@ -69,9 +71,9 @@ public class ListHabitsHelper public int getHabitNameWidth() { - float screenWidth = UIHelper.getScreenWidth(context); + float screenWidth = InterfaceUtils.getScreenWidth(context); float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth); - float padding = UIHelper.dpToPixels(context, 15); + float padding = InterfaceUtils.dpToPixels(context, 15); return (int) (screenWidth - padding - getButtonCount() * buttonWidth); } @@ -100,7 +102,7 @@ public class ListHabitsHelper public int getActiveColor(Habit habit) { - int activeColor = ColorHelper.getColor(context, habit.color); + int activeColor = ColorUtils.getColor(context, habit.color); if(habit.isArchived()) activeColor = mediumContrastColor; return activeColor; @@ -193,9 +195,9 @@ public class ListHabitsHelper Drawable background; if (isSelected) - background = UIHelper.getStyledDrawable(context, R.attr.selectedBackground); + background = InterfaceUtils.getStyledDrawable(context, R.attr.selectedBackground); else - background = UIHelper.getStyledDrawable(context, R.attr.cardBackground); + background = InterfaceUtils.getStyledDrawable(context, R.attr.cardBackground); view.setBackgroundDrawable(background); } @@ -208,20 +210,20 @@ public class ListHabitsHelper { View check = inflater.inflate(R.layout.list_habits_item_check, null); TextView btCheck = (TextView) check.findViewById(R.id.tvCheck); - btCheck.setTypeface(UIHelper.getFontAwesome(context)); + btCheck.setTypeface(InterfaceUtils.getFontAwesome(context)); btCheck.setOnLongClickListener(onLongClickListener); btCheck.setOnClickListener(onClickListener); btCheck.setHapticFeedbackEnabled(false); ((LinearLayout) view.findViewById(R.id.llButtons)).addView(check); } - view.setTag(R.id.timestamp_key, DateHelper.getStartOfToday()); + view.setTag(R.id.timestamp_key, DateUtils.getStartOfToday()); } public void updateHeader(ViewGroup header) { LayoutInflater inflater = LayoutInflater.from(context); - GregorianCalendar day = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); header.removeAllViews(); for (int i = 0; i < getButtonCount(); i++) @@ -233,7 +235,7 @@ public class ListHabitsHelper View tvDay = inflater.inflate(R.layout.list_habits_header_check, null); TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck); - btCheck.setText(DateHelper.formatHeaderDate(day)); + btCheck.setText(DateUtils.formatHeaderDate(day)); header.addView(tvDay, position); day.add(GregorianCalendar.DAY_OF_MONTH, -1); } @@ -247,7 +249,7 @@ public class ListHabitsHelper public void toggleCheckmarkView(View v, Habit habit) { - int androidColor = ColorHelper.getColor(context, habit.color); + int androidColor = ColorUtils.getColor(context, habit.color); if (v.getTag(R.string.toggle_key).equals(2)) updateCheckmark(androidColor, (TextView) v, 0); @@ -263,8 +265,8 @@ public class ListHabitsHelper public long getTimestampFromCheckmarkView(View v) { Integer offset = (Integer) v.getTag(R.string.offset_key); - return DateHelper.getStartOfDay(DateHelper.getLocalTime() - - offset * DateHelper.millisecondsInOneDay); + return DateUtils.getStartOfDay(DateUtils.getLocalTime() - + offset * DateUtils.millisecondsInOneDay); } public void triggerRipple(View v, final float x, final float y) diff --git a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsLoader.java similarity index 92% rename from app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java rename to app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsLoader.java index 12a8e9e9f..baed5b504 100644 --- a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java +++ b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsLoader.java @@ -17,16 +17,16 @@ * with this program. If not, see . */ -package org.isoron.uhabits.loaders; +package org.isoron.uhabits.ui.list; -import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; import java.util.HashMap; import java.util.List; -public class HabitListLoader +public class ListHabitsLoader { public interface Listener { @@ -66,7 +66,7 @@ public class HabitListLoader return lastLoadTimestamp; } - public HabitListLoader() + public ListHabitsLoader() { habits = new HashMap<>(); checkmarks = new HashMap<>(); @@ -103,8 +103,8 @@ public class HabitListLoader newScores = new HashMap<>(); newHabitList = Habit.getAll(includeArchived); - long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime()); - long dateFrom = dateTo - (checkmarkCount - 1) * DateHelper.millisecondsInOneDay; + long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); + long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; int[] empty = new int[checkmarkCount]; for(Habit h : newHabitList) @@ -160,7 +160,7 @@ public class HabitListLoader { if (isCancelled()) return; - lastLoadTimestamp = DateHelper.getStartOfToday(); + lastLoadTimestamp = DateUtils.getStartOfToday(); currentFetchTask = null; if(listener != null) listener.onLoadFinished(); @@ -180,8 +180,8 @@ public class HabitListLoader @Override protected void doInBackground() { - long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime()); - long dateFrom = dateTo - (checkmarkCount - 1) * DateHelper.millisecondsInOneDay; + long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); + long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; Habit h = Habit.get(id); if(h == null) return; diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java b/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java similarity index 99% rename from app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java rename to app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java index 94c574fbd..f31fad843 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/FilePickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.dialogs; +package org.isoron.uhabits.ui.settings; import android.app.Activity; import android.app.Dialog; diff --git a/app/src/main/java/org/isoron/uhabits/SettingsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java similarity index 82% rename from app/src/main/java/org/isoron/uhabits/SettingsActivity.java rename to app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java index 579e7a7a6..d9188b5c9 100644 --- a/app/src/main/java/org/isoron/uhabits/SettingsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java @@ -17,11 +17,13 @@ * with this program. If not, see . */ -package org.isoron.uhabits; +package org.isoron.uhabits.ui.settings; import android.os.Bundle; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.R; +import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.ui.BaseActivity; public class SettingsActivity extends BaseActivity { @@ -32,7 +34,7 @@ public class SettingsActivity extends BaseActivity setContentView(R.layout.settings_activity); setupSupportActionBar(true); - int color = UIHelper.getStyledColor(this, R.attr.aboutScreenColor); + int color = InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); setupActionBarColor(color); } } diff --git a/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java similarity index 90% rename from app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java rename to app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java index 838527589..9789b6790 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.fragments; +package org.isoron.uhabits.ui.settings; import android.app.backup.BackupManager; import android.content.Intent; @@ -29,8 +29,8 @@ import android.support.v7.preference.PreferenceFragmentCompat; import org.isoron.uhabits.MainActivity; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ReminderHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ReminderUtils; +import org.isoron.uhabits.utils.InterfaceUtils; public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener @@ -50,7 +50,7 @@ public class SettingsFragment extends PreferenceFragmentCompat updateRingtoneDescription(); - if(UIHelper.isLocaleFullyTranslated()) + if(InterfaceUtils.isLocaleFullyTranslated()) removePreference("translate", "linksCategory"); } @@ -111,7 +111,7 @@ public class SettingsFragment extends PreferenceFragmentCompat if (preference.getKey().equals("reminderSound")) { - ReminderHelper.startRingtonePickerActivity(this, RINGTONE_REQUEST_CODE); + ReminderUtils.startRingtonePickerActivity(this, RINGTONE_REQUEST_CODE); return true; } @@ -123,7 +123,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { if(requestCode == RINGTONE_REQUEST_CODE) { - ReminderHelper.parseRingtoneData(getContext(), data); + ReminderUtils.parseRingtoneData(getContext(), data); updateRingtoneDescription(); return; } @@ -133,7 +133,7 @@ public class SettingsFragment extends PreferenceFragmentCompat private void updateRingtoneDescription() { - String ringtoneName = ReminderHelper.getRingtoneName(getContext()); + String ringtoneName = ReminderUtils.getRingtoneName(getContext()); Preference ringtonePreference = findPreference("reminderSound"); ringtonePreference.setSummary(ringtoneName); } diff --git a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitActivity.java similarity index 88% rename from app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java rename to app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitActivity.java index 8be54b586..38a6d3d8e 100644 --- a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitActivity.java @@ -17,15 +17,17 @@ * with this program. If not, see . */ -package org.isoron.uhabits; +package org.isoron.uhabits.ui.show; import android.content.ContentUris; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBar; -import org.isoron.uhabits.helpers.ColorHelper; +import org.isoron.uhabits.R; +import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.BaseActivity; public class ShowHabitActivity extends BaseActivity { @@ -54,7 +56,7 @@ public class ShowHabitActivity extends BaseActivity actionBar.setTitle(habit.name); - setupActionBarColor(ColorHelper.getColor(this, habit.color)); + setupActionBarColor(ColorUtils.getColor(this, habit.color)); } public Habit getHabit() diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitFragment.java similarity index 89% rename from app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java rename to app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitFragment.java index f95c83881..68873a6e5 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitFragment.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.fragments; +package org.isoron.uhabits.ui.show; import android.os.Bundle; import android.support.annotation.Nullable; @@ -35,14 +35,13 @@ import android.widget.TextView; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; -import org.isoron.uhabits.ShowHabitActivity; import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.dialogs.EditHabitDialogFragment; -import org.isoron.uhabits.dialogs.HistoryEditorDialog; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.ReminderHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.ui.edit.EditHabitDialogFragment; +import org.isoron.uhabits.ui.edit.HistoryEditorDialog; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.ReminderUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; import org.isoron.uhabits.tasks.BaseTask; @@ -57,7 +56,7 @@ import java.util.LinkedList; import java.util.List; public class ShowHabitFragment extends Fragment - implements UIHelper.OnSavedListener, HistoryEditorDialog.Listener, + implements InterfaceUtils.OnSavedListener, HistoryEditorDialog.Listener, Spinner.OnItemSelectedListener { @Nullable @@ -95,8 +94,8 @@ public class ShowHabitFragment extends Fragment activity = (ShowHabitActivity) getActivity(); habit = activity.getHabit(); - activeColor = ColorHelper.getColor(getContext(), habit.color); - inactiveColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor); + activeColor = ColorUtils.getColor(getContext(), habit.color); + inactiveColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); updateHeader(view); @@ -107,7 +106,7 @@ public class ShowHabitFragment extends Fragment scoreView = (HabitScoreView) view.findViewById(R.id.scoreView); - int defaultScoreInterval = UIHelper.getDefaultScoreInterval(getContext()); + int defaultScoreInterval = InterfaceUtils.getDefaultScoreInterval(getContext()); previousScoreInterval = defaultScoreInterval; setScoreBucketSize(defaultScoreInterval); @@ -162,7 +161,7 @@ public class ShowHabitFragment extends Fragment TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel); if(habit.hasReminder()) - reminderLabel.setText(DateHelper.formatTime(getActivity(), habit.reminderHour, + reminderLabel.setText(DateUtils.formatTime(getActivity(), habit.reminderHour, habit.reminderMin)); else reminderLabel.setText(getResources().getString(R.string.reminder_off)); @@ -219,7 +218,7 @@ public class ShowHabitFragment extends Fragment float yearDiff = todayPercentage - (lastYearScore / Score.MAX_VALUE); RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); - int androidColor = ColorHelper.getColor(getActivity(), habit.color); + int androidColor = ColorUtils.getColor(getActivity(), habit.color); scoreRing.setColor(androidColor); scoreRing.setPercentage(todayPercentage); @@ -254,7 +253,7 @@ public class ShowHabitFragment extends Fragment if(habit == null || activity == null) return; TextView textView = (TextView) view.findViewById(viewId); - int androidColor = ColorHelper.getColor(activity, habit.color); + int androidColor = ColorUtils.getColor(activity, habit.color); textView.setTextColor(androidColor); } @@ -293,7 +292,7 @@ public class ShowHabitFragment extends Fragment if (h == null) activity.executeCommand(command, null); else activity.executeCommand(command, h.getId()); - ReminderHelper.createReminderAlarms(activity); + ReminderUtils.createReminderAlarms(activity); HabitBroadcastReceiver.sendRefreshBroadcast(getActivity()); activity.recreate(); @@ -316,9 +315,9 @@ public class ShowHabitFragment extends Fragment if(habit == null) return; if(dataViews == null) return; - long today = DateHelper.getStartOfToday(); - long lastMonth = today - 30 * DateHelper.millisecondsInOneDay; - long lastYear = today - 365 * DateHelper.millisecondsInOneDay; + long today = DateUtils.getStartOfToday(); + long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; + long lastYear = today - 365 * DateUtils.millisecondsInOneDay; todayScore = (float) habit.scores.getTodayValue(); lastMonthScore = (float) habit.scores.getValue(lastMonth); @@ -362,7 +361,7 @@ public class ShowHabitFragment extends Fragment HabitBroadcastReceiver.sendRefreshBroadcast(getActivity()); } - UIHelper.setDefaultScoreInterval(getContext(), position); + InterfaceUtils.setDefaultScoreInterval(getContext(), position); previousScoreInterval = position; } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ColorHelper.java b/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/helpers/ColorHelper.java rename to app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java index d64f4ac3d..1e4656540 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/ColorHelper.java +++ b/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.helpers; +package org.isoron.uhabits.utils; import android.content.Context; import android.graphics.Color; @@ -25,7 +25,7 @@ import android.util.Log; import org.isoron.uhabits.R; -public class ColorHelper +public abstract class ColorUtils { public static int CSV_PALETTE[] = { @@ -56,7 +56,7 @@ public class ColorHelper public static int[] getPalette(Context context) { - int resourceId = UIHelper.getStyleResource(context, R.attr.palette); + int resourceId = InterfaceUtils.getStyleResource(context, R.attr.palette); if(resourceId < 0) return CSV_PALETTE; return context.getResources().getIntArray(resourceId); diff --git a/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java b/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java similarity index 56% rename from app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java rename to app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java index d3c3d21e5..f8b5a9778 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java +++ b/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java @@ -17,15 +17,11 @@ * with this program. If not, see . */ -package org.isoron.uhabits.helpers; +package org.isoron.uhabits.utils; import android.content.Context; import android.database.Cursor; -import android.os.Environment; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; -import android.util.Log; import com.activeandroid.ActiveAndroid; import com.activeandroid.Cache; @@ -40,37 +36,11 @@ import org.isoron.uhabits.models.Score; import org.isoron.uhabits.models.Streak; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.text.SimpleDateFormat; -public class DatabaseHelper +public abstract class DatabaseUtils { - public static void copy(File src, File dst) throws IOException - { - FileInputStream inStream = new FileInputStream(src); - FileOutputStream outStream = new FileOutputStream(dst); - copy(inStream, outStream); - } - - public static void copy(InputStream inStream, File dst) throws IOException - { - FileOutputStream outStream = new FileOutputStream(dst); - copy(inStream, outStream); - } - - public static void copy(InputStream in, OutputStream out) throws IOException - { - int numBytes; - byte[] buffer = new byte[1024]; - - while ((numBytes = in.read(buffer)) != -1) - out.write(buffer, 0, numBytes); - } - public interface Command { void execute(); @@ -95,11 +65,11 @@ public class DatabaseHelper { File db = getDatabaseFile(); - SimpleDateFormat dateFormat = DateHelper.getBackupDateFormat(); - String date = dateFormat.format(DateHelper.getLocalTime()); + SimpleDateFormat dateFormat = DateUtils.getBackupDateFormat(); + String date = dateFormat.format(DateUtils.getLocalTime()); File dbCopy = new File(String.format("%s/Loop Habits Backup %s.db", dir.getAbsolutePath(), date)); - copy(db, dbCopy); + FileUtils.copy(db, dbCopy); return dbCopy.getAbsolutePath(); } @@ -127,63 +97,6 @@ public class DatabaseHelper return databaseFilename; } - @Nullable - public static File getSDCardDir(@Nullable String relativePath) - { - File parents[] = new File[]{ Environment.getExternalStorageDirectory() }; - return getDir(parents, relativePath); - } - - @Nullable - public static File getFilesDir(@Nullable String relativePath) - { - Context context = HabitsApplication.getContext(); - if(context == null) - { - Log.e("DatabaseHelper", "getFilesDir: no application context available"); - return null; - } - - File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null); - - if(externalFilesDirs == null) - { - Log.e("DatabaseHelper", "getFilesDir: getExternalFilesDirs returned null"); - return null; - } - - return getDir(externalFilesDirs, relativePath); - } - - @Nullable - private static File getDir(@NonNull File potentialParentDirs[], @Nullable String relativePath) - { - if(relativePath == null) relativePath = ""; - - File chosenDir = null; - for(File dir : potentialParentDirs) - { - if (dir == null || !dir.canWrite()) continue; - chosenDir = dir; - break; - } - - if(chosenDir == null) - { - Log.e("DatabaseHelper", "getDir: all potential parents are null or non-writable"); - return null; - } - - File dir = new File(String.format("%s/%s/", chosenDir.getAbsolutePath(), relativePath)); - if (!dir.exists() && !dir.mkdirs()) - { - Log.e("DatabaseHelper", "getDir: chosen dir does not exist and cannot be created"); - return null; - } - - return dir; - } - @SuppressWarnings("unchecked") public static void initializeActiveAndroid() { diff --git a/app/src/main/java/org/isoron/uhabits/helpers/DateHelper.java b/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/helpers/DateHelper.java rename to app/src/main/java/org/isoron/uhabits/utils/DateUtils.java index 4d0c02c32..a93a38c8b 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/DateHelper.java +++ b/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.helpers; +package org.isoron.uhabits.utils; import android.content.Context; import android.text.format.DateFormat; @@ -31,7 +31,7 @@ import java.util.GregorianCalendar; import java.util.Locale; import java.util.TimeZone; -public class DateHelper +public abstract class DateUtils { public static long millisecondsInOneDay = 24 * 60 * 60 * 1000; public static int ALL_WEEK_DAYS = 127; @@ -84,7 +84,7 @@ public class DateHelper public static long getStartOfToday() { - return getStartOfDay(DateHelper.getLocalTime()); + return getStartOfDay(DateUtils.getLocalTime()); } public static String formatTime(Context context, int hours, int minutes) diff --git a/app/src/main/java/org/isoron/uhabits/utils/FileUtils.java b/app/src/main/java/org/isoron/uhabits/utils/FileUtils.java new file mode 100644 index 000000000..867dbbcf0 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/utils/FileUtils.java @@ -0,0 +1,119 @@ +/* + * 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.utils; + +import android.content.Context; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.util.Log; + +import org.isoron.uhabits.HabitsApplication; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public abstract class FileUtils +{ + public static void copy(File src, File dst) throws IOException + { + FileInputStream inStream = new FileInputStream(src); + FileOutputStream outStream = new FileOutputStream(dst); + copy(inStream, outStream); + } + + public static void copy(InputStream inStream, File dst) throws IOException + { + FileOutputStream outStream = new FileOutputStream(dst); + copy(inStream, outStream); + } + + public static void copy(InputStream in, OutputStream out) throws IOException + { + int numBytes; + byte[] buffer = new byte[1024]; + + while ((numBytes = in.read(buffer)) != -1) + out.write(buffer, 0, numBytes); + } + + @Nullable + public static File getSDCardDir(@Nullable String relativePath) + { + File parents[] = new File[]{ Environment.getExternalStorageDirectory() }; + return getDir(parents, relativePath); + } + + @Nullable + public static File getFilesDir(@Nullable String relativePath) + { + Context context = HabitsApplication.getContext(); + if(context == null) + { + Log.e("DatabaseHelper", "getFilesDir: no application context available"); + return null; + } + + File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null); + + if(externalFilesDirs == null) + { + Log.e("DatabaseHelper", "getFilesDir: getExternalFilesDirs returned null"); + return null; + } + + return getDir(externalFilesDirs, relativePath); + } + + @Nullable + private static File getDir(@NonNull File potentialParentDirs[], @Nullable String relativePath) + { + if(relativePath == null) relativePath = ""; + + File chosenDir = null; + for(File dir : potentialParentDirs) + { + if (dir == null || !dir.canWrite()) continue; + chosenDir = dir; + break; + } + + if(chosenDir == null) + { + Log.e("DatabaseHelper", "getDir: all potential parents are null or non-writable"); + return null; + } + + File dir = new File(String.format("%s/%s/", chosenDir.getAbsolutePath(), relativePath)); + if (!dir.exists() && !dir.mkdirs()) + { + Log.e("DatabaseHelper", "getDir: chosen dir does not exist and cannot be created"); + return null; + } + + return dir; + } + +} diff --git a/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java rename to app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java index dfb27f1bf..5625bd7f9 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java +++ b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.helpers; +package org.isoron.uhabits.utils; import android.app.Activity; import android.content.Context; @@ -43,7 +43,7 @@ import org.isoron.uhabits.commands.Command; import java.util.Locale; -public abstract class UIHelper +public abstract class InterfaceUtils { public static final String ISORON_NAMESPACE = "http://isoron.org/android"; @@ -55,7 +55,7 @@ public abstract class UIHelper public static void setFixedTheme(Integer fixedTheme) { - UIHelper.fixedTheme = fixedTheme; + InterfaceUtils.fixedTheme = fixedTheme; } public interface OnSavedListener diff --git a/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java b/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java rename to app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java index 35e1a92a1..22abd0859 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/ReminderHelper.java +++ b/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.helpers; +package org.isoron.uhabits.utils; import android.app.AlarmManager; import android.app.PendingIntent; @@ -42,7 +42,7 @@ import java.text.DateFormat; import java.util.Calendar; import java.util.Date; -public class ReminderHelper +public abstract class ReminderUtils { public static void createReminderAlarms(Context context) { @@ -70,7 +70,7 @@ public class ReminderHelper reminderTime += AlarmManager.INTERVAL_DAY; } - long timestamp = DateHelper.getStartOfDay(DateHelper.toLocalTime(reminderTime)); + long timestamp = DateUtils.getStartOfDay(DateUtils.toLocalTime(reminderTime)); Uri uri = habit.getUri(); @@ -132,7 +132,7 @@ public class ReminderHelper public static void startRingtonePickerActivity(Fragment fragment, int requestCode) { - Uri existingRingtoneUri = ReminderHelper.getRingtoneUri(fragment.getContext()); + Uri existingRingtoneUri = ReminderUtils.getRingtoneUri(fragment.getContext()); Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI; Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java index 869c91afe..0f6046e1b 100644 --- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java @@ -27,8 +27,8 @@ import android.util.TypedValue; import android.widget.TextView; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; @@ -68,7 +68,7 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie { percentage = 0.75f; name = "Wake up early"; - activeColor = ColorHelper.CSV_PALETTE[6]; + activeColor = ColorUtils.CSV_PALETTE[6]; checkmarkValue = Checkmark.CHECKED_EXPLICITLY; refresh(); } @@ -79,7 +79,7 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie { super.setHabit(habit); this.name = habit.name; - this.activeColor = ColorHelper.getColor(getContext(), habit.color); + this.activeColor = ColorUtils.getColor(getContext(), habit.color); refresh(); } @@ -99,7 +99,7 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie text = getResources().getString(R.string.fa_check); backgroundColor = activeColor; foregroundColor = - UIHelper.getStyledColor(context, R.attr.highContrastReverseTextColor); + InterfaceUtils.getStyledColor(context, R.attr.highContrastReverseTextColor); setShadowAlpha(0x4f); rebuildBackground(); @@ -110,15 +110,15 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie case Checkmark.CHECKED_IMPLICITLY: text = getResources().getString(R.string.fa_check); - backgroundColor = UIHelper.getStyledColor(context, R.attr.cardBackgroundColor); - foregroundColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor); + backgroundColor = InterfaceUtils.getStyledColor(context, R.attr.cardBackgroundColor); + foregroundColor = InterfaceUtils.getStyledColor(context, R.attr.mediumContrastTextColor); break; case Checkmark.UNCHECKED: default: text = getResources().getString(R.string.fa_times); - backgroundColor = UIHelper.getStyledColor(context, R.attr.cardBackgroundColor); - foregroundColor = UIHelper.getStyledColor(context, R.attr.mediumContrastTextColor); + backgroundColor = InterfaceUtils.getStyledColor(context, R.attr.cardBackgroundColor); + foregroundColor = InterfaceUtils.getStyledColor(context, R.attr.mediumContrastTextColor); break; } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java index 7890757dd..7cf504508 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java @@ -26,9 +26,9 @@ import android.graphics.RectF; import android.util.AttributeSet; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import java.text.SimpleDateFormat; @@ -73,7 +73,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV public HabitFrequencyView(Context context, AttributeSet attrs) { super(context, attrs); - this.primaryColor = ColorHelper.getColor(getContext(), 7); + this.primaryColor = ColorUtils.getColor(getContext(), 7); this.frequency = new HashMap<>(); init(); } @@ -89,8 +89,8 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV createPaints(); createColors(); - dfMonth = DateHelper.getDateFormat("MMM"); - dfYear = DateHelper.getDateFormat("yyyy"); + dfMonth = DateUtils.getDateFormat("MMM"); + dfYear = DateUtils.getDateFormat("yyyy"); rect = new RectF(); prevRect = new RectF(); @@ -100,17 +100,17 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV { if(habit != null) { - this.primaryColor = ColorHelper.getColor(getContext(), habit.color); + this.primaryColor = ColorUtils.getColor(getContext(), habit.color); } - textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - gridColor = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor); + textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); + gridColor = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor); colors = new int[4]; colors[0] = gridColor; colors[3] = primaryColor; - colors[1] = ColorHelper.mixColors(colors[0], colors[3], 0.66f); - colors[2] = ColorHelper.mixColors(colors[0], colors[3], 0.33f); + colors[1] = ColorUtils.mixColors(colors[0], colors[3], 0.66f); + colors[2] = ColorUtils.mixColors(colors[0], colors[3], 0.33f); } protected void createPaints() @@ -159,7 +159,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV private float getMaxMonthWidth() { float maxMonthWidth = 0; - GregorianCalendar day = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); for(int i = 0; i < 12; i++) { @@ -183,7 +183,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV private void generateRandomData() { - GregorianCalendar date = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); date.set(Calendar.DAY_OF_MONTH, 1); Random rand = new Random(); frequency.clear(); @@ -214,7 +214,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV pGraph.setColor(primaryColor); prevRect.setEmpty(); - GregorianCalendar currentDate = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar currentDate = DateUtils.getStartOfTodayCalendar(); currentDate.set(Calendar.DAY_OF_MONTH, 1); currentDate.add(Calendar.MONTH, -nColumns + 2 - getDataOffset()); @@ -235,13 +235,13 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV float rowHeight = rect.height() / 8.0f; prevRect.set(rect); - Integer[] localeWeekdayList = DateHelper.getLocaleWeekdayList(); + Integer[] localeWeekdayList = DateUtils.getLocaleWeekdayList(); for (int j = 0; j < localeWeekdayList.length; j++) { rect.set(0, 0, baseSize, baseSize); rect.offset(prevRect.left, prevRect.top + baseSize * j); - int i = DateHelper.javaWeekdayToLoopWeekday(localeWeekdayList[j]); + int i = DateUtils.javaWeekdayToLoopWeekday(localeWeekdayList[j]); if(values != null) drawMarker(canvas, rect, values[i]); @@ -279,7 +279,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV pText.setColor(textColor); pGrid.setColor(gridColor); - for (String day : DateHelper.getLocaleDayNames(Calendar.SHORT)) { + for (String day : DateUtils.getLocaleDayNames(Calendar.SHORT)) { canvas.drawText(day, rGrid.right - columnWidth, rGrid.top + rowHeight / 2 + 0.25f * em, pText); diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index 96899585e..4c529194e 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -30,9 +30,9 @@ import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.tasks.ToggleRepetitionTask; @@ -98,20 +98,20 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie isEditable = false; checkmarks = new int[0]; - primaryColor = ColorHelper.getColor(getContext(), 7); - dfMonth = DateHelper.getDateFormat("MMM"); - dfYear = DateHelper.getDateFormat("yyyy"); + primaryColor = ColorUtils.getColor(getContext(), 7); + dfMonth = DateUtils.getDateFormat("MMM"); + dfYear = DateUtils.getDateFormat("yyyy"); baseLocation = new RectF(); } private void updateDate() { - baseDate = DateHelper.getStartOfTodayCalendar(); + baseDate = DateUtils.getStartOfTodayCalendar(); baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7); nDays = (nColumns - 1) * 7; - int realWeekday = DateHelper.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK); + int realWeekday = DateUtils.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK); todayPositionInColumn = (7 + realWeekday - baseDate.getFirstDayOfWeek()) % 7; baseDate.add(Calendar.DAY_OF_YEAR, -nDays); @@ -133,7 +133,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie float baseSize = height / 8.0f; setScrollerBucketSize((int) baseSize); - squareSpacing = UIHelper.dpToPixels(getContext(), 1.0f); + squareSpacing = InterfaceUtils.dpToPixels(getContext(), 1.0f); float maxTextSize = getResources().getDimension(R.dimen.regularTextSize); float textSize = height * 0.06f; textSize = Math.min(textSize, maxTextSize); @@ -157,7 +157,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie { float width = 0; - for(String w : DateHelper.getLocaleDayNames(Calendar.SHORT)) + for(String w : DateUtils.getLocaleDayNames(Calendar.SHORT)) width = Math.max(width, pSquareFg.measureText(w)); return width; @@ -166,10 +166,10 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie private void createColors() { if(habit != null) - this.primaryColor = ColorHelper.getColor(getContext(), habit.color); + this.primaryColor = ColorUtils.getColor(getContext(), habit.color); if(isBackgroundTransparent) - primaryColor = ColorHelper.setMinValue(primaryColor, 0.75f); + primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f); int red = Color.red(primaryColor); int green = Color.green(primaryColor); @@ -187,11 +187,11 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie else { colors = new int[3]; - colors[0] = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor); + colors[0] = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor); colors[1] = Color.argb(127, red, green, blue); colors[2] = primaryColor; - textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - reverseTextColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastReverseTextColor); + textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); + reverseTextColor = InterfaceUtils.getStyledColor(getContext(), R.attr.highContrastReverseTextColor); } } @@ -304,7 +304,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie { float verticalOffset = pTextHeader.getFontSpacing() * 0.4f; - for (String day : DateHelper.getLocaleDayNames(Calendar.SHORT)) + for (String day : DateUtils.getLocaleDayNames(Calendar.SHORT)) { location.offset(0, columnWidth); canvas.drawText(day, location.left + headerTextOffset, @@ -379,7 +379,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie Calendar date = (Calendar) baseDate.clone(); date.add(Calendar.DAY_OF_YEAR, offset); - if(DateHelper.getStartOfDay(date.getTimeInMillis()) > DateHelper.getStartOfToday()) + if(DateUtils.getStartOfDay(date.getTimeInMillis()) > DateUtils.getStartOfToday()) return null; return date.getTimeInMillis(); diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java index 6258e7b13..29c00e24b 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java @@ -31,9 +31,9 @@ import android.support.annotation.Nullable; import android.util.AttributeSet; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; @@ -92,7 +92,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView public HabitScoreView(Context context, AttributeSet attrs) { super(context, attrs); - this.primaryColor = ColorHelper.getColor(getContext(), 7); + this.primaryColor = ColorUtils.getColor(getContext(), 7); init(); } @@ -107,9 +107,9 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView createPaints(); createColors(); - dfYear = DateHelper.getDateFormat("yyyy"); - dfMonth = DateHelper.getDateFormat("MMM"); - dfDay = DateHelper.getDateFormat("d"); + dfYear = DateUtils.getDateFormat("yyyy"); + dfMonth = DateUtils.getDateFormat("MMM"); + dfDay = DateUtils.getDateFormat("d"); rect = new RectF(); prevRect = new RectF(); @@ -118,11 +118,11 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView private void createColors() { if(habit != null) - this.primaryColor = ColorHelper.getColor(getContext(), habit.color); + this.primaryColor = ColorUtils.getColor(getContext(), habit.color); - textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - gridColor = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor); - backgroundColor = UIHelper.getStyledColor(getContext(), R.attr.cardBackgroundColor); + textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); + gridColor = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor); + backgroundColor = InterfaceUtils.getStyledColor(getContext(), R.attr.cardBackgroundColor); } protected void createPaints() @@ -171,7 +171,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView columnHeight = 8 * baseSize; - float minStrokeWidth = UIHelper.dpToPixels(getContext(), 1); + float minStrokeWidth = InterfaceUtils.dpToPixels(getContext(), 1); pGraph.setTextSize(baseSize * 0.5f); pGraph.setStrokeWidth(baseSize * 0.1f); pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f)); @@ -252,10 +252,10 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView previousYearText = ""; skipYear = 0; - long currentDate = DateHelper.getStartOfToday(); + long currentDate = DateUtils.getStartOfToday(); for(int k = 0; k < nColumns + getDataOffset() - 1; k++) - currentDate -= bucketSize * DateHelper.millisecondsInOneDay; + currentDate -= bucketSize * DateUtils.millisecondsInOneDay; for (int k = 0; k < nColumns; k++) { @@ -284,7 +284,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView drawFooter(activeCanvas, rect, currentDate); - currentDate += bucketSize * DateHelper.millisecondsInOneDay; + currentDate += bucketSize * DateUtils.millisecondsInOneDay; } if(activeCanvas != canvas) @@ -301,7 +301,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView String monthText = dfMonth.format(currentDate); String dayText = dfDay.format(currentDate); - GregorianCalendar calendar = DateHelper.getCalendar(currentDate); + GregorianCalendar calendar = DateUtils.getCalendar(currentDate); String text; int year = calendar.get(Calendar.YEAR); @@ -408,7 +408,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView private float getMaxMonthWidth() { float maxMonthWidth = 0; - GregorianCalendar day = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); for(int i = 0; i < 12; i++) { @@ -423,7 +423,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView private float getMaxDayWidth() { float maxDayWidth = 0; - GregorianCalendar day = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); for(int i = 0; i < 28; i++) { diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java index 74fc6c518..27959fddf 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java @@ -28,8 +28,8 @@ import android.util.AttributeSet; import android.view.View; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Streak; @@ -73,7 +73,7 @@ public class HabitStreakView extends View implements HabitDataView public HabitStreakView(Context context, AttributeSet attrs) { super(context, attrs); - this.primaryColor = ColorHelper.getColor(getContext(), 7); + this.primaryColor = ColorUtils.getColor(getContext(), 7); init(); } @@ -125,7 +125,7 @@ public class HabitStreakView extends View implements HabitDataView private void createColors() { if(habit != null) - this.primaryColor = ColorHelper.getColor(getContext(), habit.color); + this.primaryColor = ColorUtils.getColor(getContext(), habit.color); int red = Color.red(primaryColor); int green = Color.green(primaryColor); @@ -135,9 +135,9 @@ public class HabitStreakView extends View implements HabitDataView colors[3] = primaryColor; colors[2] = Color.argb(192, red, green, blue); colors[1] = Color.argb(96, red, green, blue); - colors[0] = UIHelper.getStyledColor(getContext(), R.attr.lowContrastTextColor); - textColor = UIHelper.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - reverseTextColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastReverseTextColor); + colors[0] = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor); + textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); + reverseTextColor = InterfaceUtils.getStyledColor(getContext(), R.attr.highContrastReverseTextColor); } protected void createPaints() diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java b/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java index bab859aaa..b7dc4a918 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java @@ -32,7 +32,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import java.util.Arrays; @@ -71,7 +71,7 @@ public abstract class HabitWidgetView extends FrameLayout implements HabitDataV private void init() { inflate(getContext(), getInnerLayoutId(), this); - shadowAlpha = (int) (255 * UIHelper.getStyledFloat(getContext(), R.attr.widgetShadowAlpha)); + shadowAlpha = (int) (255 * InterfaceUtils.getStyledFloat(getContext(), R.attr.widgetShadowAlpha)); rebuildBackground(); } @@ -82,13 +82,13 @@ public abstract class HabitWidgetView extends FrameLayout implements HabitDataV Context context = getContext(); int backgroundAlpha = - (int) (255 * UIHelper.getStyledFloat(context, R.attr.widgetBackgroundAlpha)); + (int) (255 * InterfaceUtils.getStyledFloat(context, R.attr.widgetBackgroundAlpha)); - int shadowRadius = (int) UIHelper.dpToPixels(context, 2); - int shadowOffset = (int) UIHelper.dpToPixels(context, 1); + int shadowRadius = (int) InterfaceUtils.dpToPixels(context, 2); + int shadowOffset = (int) InterfaceUtils.dpToPixels(context, 1); int shadowColor = Color.argb(shadowAlpha, 0, 0, 0); - float cornerRadius = UIHelper.dpToPixels(context, 5); + float cornerRadius = InterfaceUtils.dpToPixels(context, 5); float[] radii = new float[8]; Arrays.fill(radii, cornerRadius); @@ -102,7 +102,7 @@ public abstract class HabitWidgetView extends FrameLayout implements HabitDataV insetRightBottom); backgroundPaint = innerDrawable.getPaint(); backgroundPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, shadowColor); - backgroundPaint.setColor(UIHelper.getStyledColor(context, R.attr.cardBackgroundColor)); + backgroundPaint.setColor(InterfaceUtils.getStyledColor(context, R.attr.cardBackgroundColor)); backgroundPaint.setAlpha(backgroundAlpha); frame = (ViewGroup) findViewById(R.id.frame); diff --git a/app/src/main/java/org/isoron/uhabits/views/NumberView.java b/app/src/main/java/org/isoron/uhabits/views/NumberView.java index b5412a7a3..7b788d12a 100644 --- a/app/src/main/java/org/isoron/uhabits/views/NumberView.java +++ b/app/src/main/java/org/isoron/uhabits/views/NumberView.java @@ -32,8 +32,8 @@ import android.util.AttributeSet; import android.view.View; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.InterfaceUtils; public class NumberView extends View { @@ -66,12 +66,12 @@ public class NumberView extends View this.textSize = getResources().getDimension(R.dimen.regularTextSize); - this.label = UIHelper.getAttribute(context, attrs, "label", "Number"); - this.number = UIHelper.getIntAttribute(context, attrs, "number", 0); - this.textSize = UIHelper.getFloatAttribute(context, attrs, "textSize", + this.label = InterfaceUtils.getAttribute(context, attrs, "label", "Number"); + this.number = InterfaceUtils.getIntAttribute(context, attrs, "number", 0); + this.textSize = InterfaceUtils.getFloatAttribute(context, attrs, "textSize", getResources().getDimension(R.dimen.regularTextSize)); - this.color = ColorHelper.getColor(getContext(), 7); + this.color = ColorUtils.getColor(getContext(), 7); init(); } diff --git a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java index ee850ab2f..0ddfcea58 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java @@ -23,9 +23,9 @@ import android.content.Context; import android.util.AttributeSet; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import java.util.Calendar; @@ -39,9 +39,9 @@ public class RepetitionCountView extends NumberView implements HabitDataView public RepetitionCountView(Context context, AttributeSet attrs) { super(context, attrs); - this.interval = UIHelper.getIntAttribute(context, attrs, "interval", 7); - int labelValue = UIHelper.getIntAttribute(context, attrs, "labelValue", 7); - String labelFormat = UIHelper.getAttribute(context, attrs, "labelFormat", + this.interval = InterfaceUtils.getIntAttribute(context, attrs, "interval", 7); + int labelValue = InterfaceUtils.getIntAttribute(context, attrs, "labelValue", 7); + String labelFormat = InterfaceUtils.getAttribute(context, attrs, "labelFormat", getResources().getString(R.string.last_x_days)); setLabel(String.format(labelFormat, labelValue)); @@ -56,7 +56,7 @@ public class RepetitionCountView extends NumberView implements HabitDataView return; } - long to = DateHelper.getStartOfToday(); + long to = DateUtils.getStartOfToday(); long from; if(interval == 0) @@ -65,7 +65,7 @@ public class RepetitionCountView extends NumberView implements HabitDataView } else { - GregorianCalendar fromCalendar = DateHelper.getStartOfTodayCalendar(); + GregorianCalendar fromCalendar = DateUtils.getStartOfTodayCalendar(); fromCalendar.add(Calendar.DAY_OF_YEAR, -interval + 1); from = fromCalendar.getTimeInMillis(); } @@ -80,6 +80,6 @@ public class RepetitionCountView extends NumberView implements HabitDataView public void setHabit(Habit habit) { this.habit = habit; - setColor(ColorHelper.getColor(getContext(), habit.color)); + setColor(ColorUtils.getColor(getContext(), habit.color)); } } diff --git a/app/src/main/java/org/isoron/uhabits/views/RingView.java b/app/src/main/java/org/isoron/uhabits/views/RingView.java index cac641a22..f521fc47d 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RingView.java @@ -33,8 +33,8 @@ import android.util.AttributeSet; import android.view.View; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.InterfaceUtils; public class RingView extends View { @@ -70,8 +70,8 @@ public class RingView extends View percentage = 0.0f; precision = 0.01f; - color = ColorHelper.CSV_PALETTE[0]; - thickness = UIHelper.dpToPixels(getContext(), 2); + color = ColorUtils.CSV_PALETTE[0]; + thickness = InterfaceUtils.dpToPixels(getContext(), 2); text = ""; textSize = context.getResources().getDimension(R.dimen.smallTextSize); @@ -82,23 +82,23 @@ public class RingView extends View { super(context, attrs); - percentage = UIHelper.getFloatAttribute(context, attrs, "percentage", 0); - precision = UIHelper.getFloatAttribute(context, attrs, "precision", 0.01f); + percentage = InterfaceUtils.getFloatAttribute(context, attrs, "percentage", 0); + precision = InterfaceUtils.getFloatAttribute(context, attrs, "precision", 0.01f); - color = UIHelper.getColorAttribute(context, attrs, "color", 0); - backgroundColor = UIHelper.getColorAttribute(context, attrs, "backgroundColor", null); - inactiveColor = UIHelper.getColorAttribute(context, attrs, "inactiveColor", null); + color = InterfaceUtils.getColorAttribute(context, attrs, "color", 0); + backgroundColor = InterfaceUtils.getColorAttribute(context, attrs, "backgroundColor", null); + inactiveColor = InterfaceUtils.getColorAttribute(context, attrs, "inactiveColor", null); - thickness = UIHelper.getFloatAttribute(context, attrs, "thickness", 0); - thickness = UIHelper.dpToPixels(context, thickness); + thickness = InterfaceUtils.getFloatAttribute(context, attrs, "thickness", 0); + thickness = InterfaceUtils.dpToPixels(context, thickness); float defaultTextSize = context.getResources().getDimension(R.dimen.smallTextSize); - textSize = UIHelper.getFloatAttribute(context, attrs, "textSize", defaultTextSize); - textSize = UIHelper.spToPixels(context, textSize); + textSize = InterfaceUtils.getFloatAttribute(context, attrs, "textSize", defaultTextSize); + textSize = InterfaceUtils.spToPixels(context, textSize); - text = UIHelper.getAttribute(context, attrs, "text", ""); + text = InterfaceUtils.getAttribute(context, attrs, "text", ""); - enableFontAwesome = UIHelper.getBooleanAttribute(context, attrs, "enableFontAwesome", false); + enableFontAwesome = InterfaceUtils.getBooleanAttribute(context, attrs, "enableFontAwesome", false); init(); } @@ -153,12 +153,12 @@ public class RingView extends View pRing.setTextAlign(Paint.Align.CENTER); if(backgroundColor == null) - backgroundColor = UIHelper.getStyledColor(getContext(), R.attr.cardBackgroundColor); + backgroundColor = InterfaceUtils.getStyledColor(getContext(), R.attr.cardBackgroundColor); if(inactiveColor == null) - inactiveColor = UIHelper.getStyledColor(getContext(), R.attr.highContrastTextColor); + inactiveColor = InterfaceUtils.getStyledColor(getContext(), R.attr.highContrastTextColor); - inactiveColor = ColorHelper.setAlpha(inactiveColor, 0.1f); + inactiveColor = ColorUtils.setAlpha(inactiveColor, 0.1f); rect = new RectF(); } @@ -233,7 +233,7 @@ public class RingView extends View pRing.setColor(color); pRing.setTextSize(textSize); - if(enableFontAwesome) pRing.setTypeface(UIHelper.getFontAwesome(getContext())); + if(enableFontAwesome) pRing.setTypeface(InterfaceUtils.getFontAwesome(getContext())); activeCanvas.drawText(text, rect.centerX(), rect.centerY() + 0.4f * em, pRing); } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java index 9c36fe398..686255ece 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -36,7 +36,7 @@ import android.widget.RemoteViews; import android.widget.TextView; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; @@ -170,13 +170,13 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - maxWidth = (int) UIHelper.dpToPixels(context, + maxWidth = (int) InterfaceUtils.dpToPixels(context, options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)); - maxHeight = (int) UIHelper.dpToPixels(context, + maxHeight = (int) InterfaceUtils.dpToPixels(context, options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)); - minWidth = (int) UIHelper.dpToPixels(context, + minWidth = (int) InterfaceUtils.dpToPixels(context, options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)); - minHeight = (int) UIHelper.dpToPixels(context, + minHeight = (int) InterfaceUtils.dpToPixels(context, options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)); } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java index d09881acf..9d4f187c3 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java @@ -33,7 +33,6 @@ import android.widget.ListView; import org.isoron.uhabits.MainActivity; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.widgets.BaseWidgetProvider; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java index 2608887b4..a046dc70d 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java @@ -24,7 +24,7 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.views.GraphWidgetView; import org.isoron.uhabits.views.HabitDataView; @@ -35,7 +35,7 @@ public class ScoreWidgetProvider extends BaseWidgetProvider @Override protected View buildCustomView(Context context, Habit habit) { - int defaultScoreInterval = UIHelper.getDefaultScoreInterval(context); + int defaultScoreInterval = InterfaceUtils.getDefaultScoreInterval(context); int size = HabitScoreView.DEFAULT_BUCKET_SIZES[defaultScoreInterval]; HabitScoreView dataView = new HabitScoreView(context); diff --git a/app/src/main/res/layout/edit_habit.xml b/app/src/main/res/layout/edit_habit.xml index bc2f0a98c..cbca604bf 100644 --- a/app/src/main/res/layout/edit_habit.xml +++ b/app/src/main/res/layout/edit_habit.xml @@ -22,7 +22,7 @@ style="@style/dialogForm" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - tools:context=".dialogs.EditHabitDialogFragment" + tools:context=".ui.edit.EditHabitDialogFragment" tools:ignore="MergeRootFrame"> + tools:context=".ui.show.ShowHabitActivity"> @@ -35,7 +35,7 @@ + tools:context=".MainActivity"> + tools:context=".MainActivity"> From 6445bf62bcaeffafa81d2ab993f864fecb10a8d3 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Thu, 26 May 2016 16:35:47 -0400 Subject: [PATCH 003/184] Refactor MainActivity and Preferences --- .../uhabits/unit/HabitsApplicationTest.java | 8 +- .../uhabits/unit/tasks/ExportCSVTaskTest.java | 4 +- .../uhabits/unit/tasks/ExportDBTaskTest.java | 8 +- .../unit/tasks/ImportDataTaskTest.java | 9 +- .../uhabits/HabitBroadcastReceiver.java | 5 +- .../org/isoron/uhabits/HabitsApplication.java | 83 ++++- .../java/org/isoron/uhabits/MainActivity.java | 292 ++++++----------- .../org/isoron/uhabits/MainController.java | 179 +++++++++++ .../java/org/isoron/uhabits/Preferences.java | 81 +++++ .../isoron/uhabits/tasks/ExportCSVTask.java | 15 +- .../isoron/uhabits/tasks/ExportDBTask.java | 17 +- .../isoron/uhabits/tasks/ImportDataTask.java | 17 +- .../org/isoron/uhabits/tasks/ProgressBar.java | 26 ++ .../isoron/uhabits/ui/AndroidProgressBar.java | 47 +++ .../org/isoron/uhabits/ui/BaseActivity.java | 12 +- .../uhabits/ui/list/ListHabitsController.java | 37 +++ .../uhabits/ui/list/ListHabitsFragment.java | 302 ++++++------------ .../uhabits/ui/settings/SettingsFragment.java | 10 +- .../isoron/uhabits/utils/InterfaceUtils.java | 29 -- .../uhabits/widgets/HabitPickerDialog.java | 3 +- .../isoron/uhabits/widgets/WidgetManager.java | 47 +++ 21 files changed, 731 insertions(+), 500 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/MainController.java create mode 100644 app/src/main/java/org/isoron/uhabits/Preferences.java create mode 100644 app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsController.java create mode 100644 app/src/main/java/org/isoron/uhabits/widgets/WidgetManager.java diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java index 1c4f78ad8..72e721d37 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java @@ -23,6 +23,7 @@ import android.os.Build; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.BaseTest; import org.isoron.uhabits.HabitsApplication; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,7 +35,7 @@ import static org.hamcrest.Matchers.containsString; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitsApplicationTest +public class HabitsApplicationTest extends BaseTest { @Test public void test_getLogcat() throws IOException @@ -45,7 +46,10 @@ public class HabitsApplicationTest String msg = "LOGCAT TEST"; new RuntimeException(msg).printStackTrace(); - String log = HabitsApplication.getLogcat(); + HabitsApplication app = HabitsApplication.getInstance(); + assert(app != null); + + String log = app.getLogcat(); assertThat(log, containsString(msg)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java index f827dddf4..384d94b94 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java @@ -21,7 +21,6 @@ package org.isoron.uhabits.unit.tasks; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import android.widget.ProgressBar; import org.isoron.uhabits.BaseTest; import org.isoron.uhabits.models.Habit; @@ -55,9 +54,8 @@ public class ExportCSVTaskTest extends BaseTest { HabitFixtures.createShortHabit(); List habits = Habit.getAll(true); - ProgressBar bar = new ProgressBar(targetContext); - ExportCSVTask task = new ExportCSVTask(habits, bar); + ExportCSVTask task = new ExportCSVTask(habits, null); task.setListener(new ExportCSVTask.Listener() { @Override diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java index 26269e353..5dca0ec33 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java @@ -19,11 +19,8 @@ package org.isoron.uhabits.unit.tasks; -import android.content.Context; -import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import android.widget.ProgressBar; import org.isoron.uhabits.BaseTest; import org.isoron.uhabits.tasks.ExportDBTask; @@ -52,10 +49,7 @@ public class ExportDBTaskTest extends BaseTest @Test public void testExportCSV() throws Throwable { - Context context = InstrumentationRegistry.getContext(); - - ProgressBar bar = new ProgressBar(context); - ExportDBTask task = new ExportDBTask(bar); + ExportDBTask task = new ExportDBTask(null); task.setListener(new ExportDBTask.Listener() { @Override diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java index 833675896..8e67cc883 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java @@ -22,11 +22,10 @@ package org.isoron.uhabits.unit.tasks; import android.support.annotation.NonNull; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import android.widget.ProgressBar; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.tasks.ImportDataTask; +import org.isoron.uhabits.utils.FileUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,7 +66,7 @@ public class ImportDataTaskTest extends BaseTest task.setListener(new ImportDataTask.Listener() { @Override - public void onImportFinished(int result) + public void onImportDataFinished(int result) { assertThat(result, equalTo(expectedResult)); } @@ -80,11 +79,9 @@ public class ImportDataTaskTest extends BaseTest @NonNull private ImportDataTask createTask(String assetFilename) throws IOException { - ProgressBar bar = new ProgressBar(targetContext); File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename)); copyAssetToFile(assetFilename, file); - - return new ImportDataTask(file, bar); + return new ImportDataTask(file, null); } @Test diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java index c4adf9394..a6fa0a01e 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java @@ -42,6 +42,7 @@ import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.ui.show.ShowHabitActivity; +import org.isoron.uhabits.widgets.WidgetManager; import java.util.Date; @@ -123,10 +124,10 @@ public class HabitBroadcastReceiver extends BroadcastReceiver public static void sendRefreshBroadcast(Context context) { LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context); - Intent refreshIntent = new Intent(MainActivity.ACTION_REFRESH); + Intent refreshIntent = new Intent(HabitsApplication.ACTION_REFRESH); manager.sendBroadcast(refreshIntent); - MainActivity.updateWidgets(context); + WidgetManager.updateWidgets(context); } private void dismissAllHabits() diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index 076b30e7d..ab69642b4 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -21,6 +21,8 @@ package org.isoron.uhabits; import android.app.Application; import android.content.Context; +import android.content.Intent; +import android.net.Uri; import android.os.Environment; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -28,9 +30,12 @@ import android.view.WindowManager; import com.activeandroid.ActiveAndroid; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.ReminderUtils; +import org.isoron.uhabits.widgets.WidgetManager; import java.io.BufferedReader; import java.io.File; @@ -39,8 +44,17 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.LinkedList; -public class HabitsApplication extends Application +public class HabitsApplication extends Application implements MainController.System { + public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH"; + public static final int RESULT_IMPORT_DATA = 1; + public static final int RESULT_EXPORT_CSV = 2; + public static final int RESULT_EXPORT_DB = 3; + public static final int RESULT_BUG_REPORT = 4; + + @Nullable + private static HabitsApplication application; + @Nullable private static Context context; @@ -64,11 +78,18 @@ public class HabitsApplication extends Application return context; } + @Nullable + public static HabitsApplication getInstance() + { + return application; + } + @Override public void onCreate() { super.onCreate(); HabitsApplication.context = this; + HabitsApplication.application = this; if (isTestMode()) { @@ -87,7 +108,7 @@ public class HabitsApplication extends Application super.onTerminate(); } - public static String getLogcat() throws IOException + public String getLogcat() throws IOException { int maxNLines = 250; StringBuilder builder = new StringBuilder(); @@ -116,7 +137,7 @@ public class HabitsApplication extends Application return builder.toString(); } - public static String getDeviceInfo() + public String getDeviceInfo() { if(context == null) return ""; @@ -125,7 +146,7 @@ public class HabitsApplication extends Application b.append(String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME)); b.append(String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE)); - b.append(String.format("OS Version: %s (%s)\n", System.getProperty("os.version"), + b.append(String.format("OS Version: %s (%s)\n", java.lang.System.getProperty("os.version"), android.os.Build.VERSION.INCREMENTAL)); b.append(String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK)); b.append(String.format("Device: %s\n", android.os.Build.DEVICE)); @@ -141,7 +162,7 @@ public class HabitsApplication extends Application } @NonNull - public static File dumpBugReportToFile() throws IOException + public File dumpBugReportToFile() throws IOException { String date = DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); @@ -151,17 +172,63 @@ public class HabitsApplication extends Application File logFile = new File(String.format("%s/Log %s.txt", dir.getPath(), date)); FileWriter output = new FileWriter(logFile); - output.write(generateBugReport()); + output.write(getBugReport()); output.close(); return logFile; } @NonNull - public static String generateBugReport() throws IOException + public String getBugReport() throws IOException { String logcat = getLogcat(); String deviceInfo = getDeviceInfo(); return deviceInfo + "\n" + logcat; } + + public void sendFile(@NonNull String archiveFilename) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/zip"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); + startActivity(intent); + } + + public void sendEmail(String to, String subject, String content) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("message/rfc822"); + intent.putExtra(Intent.EXTRA_EMAIL, new String[] {to}); + intent.putExtra(Intent.EXTRA_SUBJECT, subject); + intent.putExtra(Intent.EXTRA_TEXT, content); + startActivity(intent); + } + + public void scheduleReminders() + { + new BaseTask() + { + + @Override + protected void doInBackground() + { + ReminderUtils.createReminderAlarms(getContext()); + } + }.execute(); + } + + public void updateWidgets() + { + new BaseTask() + { + + @Override + protected void doInBackground() + { + WidgetManager.updateWidgets(getContext()); + } + }.execute(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 6703eb40c..dbf4b4f2b 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -19,86 +19,62 @@ package org.isoron.uhabits; -import android.appwidget.AppWidgetManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.graphics.drawable.ColorDrawable; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.v4.content.LocalBroadcastManager; +import android.support.v4.app.FragmentManager; import android.support.v7.app.ActionBar; import android.view.Menu; import android.view.MenuItem; -import org.isoron.uhabits.ui.list.ListHabitsFragment; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.ReminderUtils; -import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.tasks.ProgressBar; import org.isoron.uhabits.ui.AboutActivity; +import org.isoron.uhabits.ui.AndroidProgressBar; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.ui.IntroActivity; +import org.isoron.uhabits.ui.list.ListHabitsFragment; +import org.isoron.uhabits.ui.settings.FilePickerDialog; import org.isoron.uhabits.ui.settings.SettingsActivity; import org.isoron.uhabits.ui.show.ShowHabitActivity; -import org.isoron.uhabits.widgets.CheckmarkWidgetProvider; -import org.isoron.uhabits.widgets.FrequencyWidgetProvider; -import org.isoron.uhabits.widgets.HistoryWidgetProvider; -import org.isoron.uhabits.widgets.ScoreWidgetProvider; -import org.isoron.uhabits.widgets.StreakWidgetProvider; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.widgets.WidgetManager; -import java.io.IOException; +import java.io.File; public class MainActivity extends BaseActivity - implements ListHabitsFragment.OnHabitClickListener + implements ListHabitsFragment.OnHabitClickListener, MainController.Screen { + private MainController controller; private ListHabitsFragment listHabitsFragment; - private SharedPreferences prefs; - private BroadcastReceiver receiver; - private LocalBroadcastManager localBroadcastManager; - - public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH"; - - public static final int RESULT_IMPORT_DATA = 1; - public static final int RESULT_EXPORT_CSV = 2; - public static final int RESULT_EXPORT_DB = 3; - public static final int RESULT_BUG_REPORT = 4; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.list_habits_activity); - setupSupportActionBar(false); - prefs = PreferenceManager.getDefaultSharedPreferences(this); - listHabitsFragment = - (ListHabitsFragment) getSupportFragmentManager().findFragmentById(R.id.fragment1); - - receiver = new Receiver(); - localBroadcastManager = LocalBroadcastManager.getInstance(this); - localBroadcastManager.registerReceiver(receiver, new IntentFilter(ACTION_REFRESH)); + FragmentManager fragmentManager = getSupportFragmentManager(); + listHabitsFragment = (ListHabitsFragment) fragmentManager.findFragmentById(R.id.fragment1); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) - onPreLollipopStartup(); + onPreLollipopCreate(); - onStartup(); + controller = new MainController(); + controller.setScreen(this); + controller.setSystem((HabitsApplication) getApplication()); + controller.onStartup(); } - private void onPreLollipopStartup() + private void onPreLollipopCreate() { ActionBar actionBar = getSupportActionBar(); if(actionBar == null) return; @@ -108,50 +84,13 @@ public class MainActivity extends BaseActivity actionBar.setBackgroundDrawable(new ColorDrawable(color)); } - private void onStartup() - { - PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - InterfaceUtils.incrementLaunchCount(this); - InterfaceUtils.updateLastAppVersion(this); - showTutorial(); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) - { - ReminderUtils.createReminderAlarms(MainActivity.this); - updateWidgets(MainActivity.this); - return null; - } - }.execute(); - - } - - private void showTutorial() - { - Boolean firstRun = prefs.getBoolean("pref_first_run", true); - - if (firstRun) - { - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean("pref_first_run", false); - editor.putLong("last_hint_timestamp", DateUtils.getStartOfToday()).apply(); - editor.apply(); - - Intent intent = new Intent(this, IntroActivity.class); - this.startActivity(intent); - } - } - @Override public boolean onCreateOptionsMenu(Menu menu) { menu.clear(); getMenuInflater().inflate(R.menu.list_habits_menu, menu); - MenuItem nightModeItem = menu.findItem(R.id.action_night_mode); nightModeItem.setChecked(InterfaceUtils.isNightMode()); - return true; } @@ -161,122 +100,100 @@ public class MainActivity extends BaseActivity switch (item.getItemId()) { case R.id.action_night_mode: - { - if(InterfaceUtils.isNightMode()) - InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_LIGHT); - else - InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_DARK); - - refreshTheme(); + toggleNightMode(); return true; - } case R.id.action_settings: - { - Intent intent = new Intent(this, SettingsActivity.class); - startActivityForResult(intent, 0); + showSettingsScreen(); return true; - } case R.id.action_about: - { - Intent intent = new Intent(this, AboutActivity.class); - startActivity(intent); + showAboutScreen(); return true; - } case R.id.action_faq: - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(getString(R.string.helpURL))); - startActivity(intent); + showFAQScreen(); return true; - } default: return super.onOptionsItemSelected(item); } } - private void refreshTheme() - { - new Handler().postDelayed(new Runnable() - { - @Override - public void run() - { - Intent intent = new Intent(MainActivity.this, MainActivity.class); - - MainActivity.this.finish(); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - startActivity(intent); - - } - }, 500); // Let the menu disappear first - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (resultCode) { - case RESULT_IMPORT_DATA: - listHabitsFragment.showImportDialog(); + case HabitsApplication.RESULT_IMPORT_DATA: + showImportScreen(); break; - case RESULT_EXPORT_CSV: - listHabitsFragment.exportAllHabits(); + case HabitsApplication.RESULT_EXPORT_CSV: + controller.exportCSV(); break; - case RESULT_EXPORT_DB: - listHabitsFragment.exportDB(); + case HabitsApplication.RESULT_EXPORT_DB: + controller.exportDB(); break; - case RESULT_BUG_REPORT: - generateBugReport(); + case HabitsApplication.RESULT_BUG_REPORT: + controller.sendBugReport(); break; } } - private void generateBugReport() + private void showFAQScreen() { - try - { - HabitsApplication.dumpBugReportToFile(); - } - catch (IOException e) - { - // ignored - } + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(getString(R.string.helpURL))); + startActivity(intent); + } - try - { - String log = "---------- BUG REPORT BEGINS ----------\n"; - log += HabitsApplication.generateBugReport(); - log += "---------- BUG REPORT ENDS ------------\n"; - - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("message/rfc822"); - intent.putExtra(Intent.EXTRA_EMAIL, new String[] { "dev@loophabits.org" }); - intent.putExtra(Intent.EXTRA_SUBJECT, "Bug Report - Loop Habit Tracker"); - intent.putExtra(Intent.EXTRA_TEXT, log); - startActivity(intent); - } - catch (IOException e) + private void showAboutScreen() + { + Intent intent = new Intent(this, AboutActivity.class); + startActivity(intent); + } + + private void showSettingsScreen() + { + Intent intent = new Intent(this, SettingsActivity.class); + startActivityForResult(intent, 0); + } + + private void toggleNightMode() + { + if(InterfaceUtils.isNightMode()) + InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_LIGHT); + else + InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_DARK); + + refreshTheme(); + } + + private void refreshTheme() + { + new Handler().postDelayed(new Runnable() { - e.printStackTrace(); - showToast(R.string.bug_report_failed); - } + @Override + public void run() + { + Intent intent = new Intent(MainActivity.this, MainActivity.class); + + MainActivity.this.finish(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + startActivity(intent); + + } + }, 500); // Let the menu disappear first } @Override public void onHabitClicked(Habit habit) { - Intent intent = new Intent(this, ShowHabitActivity.class); - intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId())); - startActivity(intent); + showHabitScreen(habit); } @Override @@ -290,7 +207,7 @@ public class MainActivity extends BaseActivity protected void doInBackground() { dismissNotifications(MainActivity.this); - updateWidgets(MainActivity.this); + WidgetManager.updateWidgets(MainActivity.this); } }.execute(); } @@ -304,48 +221,49 @@ public class MainActivity extends BaseActivity } } - public static void updateWidgets(Context context) - { - updateWidgets(context, CheckmarkWidgetProvider.class); - updateWidgets(context, HistoryWidgetProvider.class); - updateWidgets(context, ScoreWidgetProvider.class); - updateWidgets(context, StreakWidgetProvider.class); - updateWidgets(context, FrequencyWidgetProvider.class); - } - - private static void updateWidgets(Context context, Class providerClass) + private void showHabitScreen(Habit habit) { - ComponentName provider = new ComponentName(context, providerClass); - Intent intent = new Intent(context, providerClass); - intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - int ids[] = AppWidgetManager.getInstance(context).getAppWidgetIds(provider); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids); - context.sendBroadcast(intent); + Intent intent = new Intent(this, ShowHabitActivity.class); + intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId())); + startActivity(intent); } - @Override - protected void onDestroy() + public void showIntroScreen() { - localBroadcastManager.unregisterReceiver(receiver); - super.onDestroy(); + Intent intent = new Intent(this, IntroActivity.class); + this.startActivity(intent); } - class Receiver extends BroadcastReceiver + public void showImportScreen() { - @Override - public void onReceive(Context context, Intent intent) + File dir = FileUtils.getFilesDir(null); + if(dir == null) { - listHabitsFragment.onPostExecuteCommand(null); + showMessage(R.string.could_not_import); + return; } + + FilePickerDialog picker = new FilePickerDialog(this, dir); + picker.setListener(new FilePickerDialog.OnFileSelectedListener() + { + @Override + public void onFileSelected(File file) + { + controller.importData(file); + } + }); + picker.show(); } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) + public void refresh(Long refreshKey) { - if (grantResults.length <= 0) return; - if (grantResults[0] != PackageManager.PERMISSION_GRANTED) return; + listHabitsFragment.loader.updateAllHabits(true); + } - listHabitsFragment.showImportDialog(); + @Override + public ProgressBar getProgressBar() + { + return new AndroidProgressBar(listHabitsFragment.progressBar); } } diff --git a/app/src/main/java/org/isoron/uhabits/MainController.java b/app/src/main/java/org/isoron/uhabits/MainController.java new file mode 100644 index 000000000..d857d012f --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/MainController.java @@ -0,0 +1,179 @@ +/* + * 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; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.ExportCSVTask; +import org.isoron.uhabits.tasks.ExportDBTask; +import org.isoron.uhabits.tasks.ImportDataTask; +import org.isoron.uhabits.tasks.ProgressBar; +import org.isoron.uhabits.utils.DateUtils; + +import java.io.File; +import java.io.IOException; + +public class MainController implements ImportDataTask.Listener, ExportCSVTask.Listener, + ExportDBTask.Listener +{ + public interface Screen + { + void showIntroScreen(); + + void showMessage(Integer stringId); + + void refresh(Long refreshKey); + + ProgressBar getProgressBar(); + } + + public interface System + { + void sendFile(String filename); + + void sendEmail(String to, String subject, String content); + + void scheduleReminders(); + + void updateWidgets(); + + File dumpBugReportToFile() throws IOException; + + String getBugReport() throws IOException; + } + + System sys; + Screen screen; + Preferences prefs; + + public MainController() + { + prefs = Preferences.getInstance(); + } + + public void setScreen(Screen screen) + { + this.screen = screen; + } + + public void setSystem(System sys) + { + this.sys = sys; + } + + public void onStartup() + { + prefs.initialize(); + prefs.incrementLaunchCount(); + prefs.updateLastAppVersion(); + if(prefs.isFirstRun()) onFirstRun(); + + sys.updateWidgets(); + sys.scheduleReminders(); + } + + private void onFirstRun() + { + prefs.setFirstRun(false); + prefs.setLastHintTimestamp(DateUtils.getStartOfToday()); + screen.showIntroScreen(); + } + + public void importData(File file) + { + ImportDataTask task = new ImportDataTask(file, screen.getProgressBar()); + task.setListener(this); + task.execute(); + } + + @Override + public void onImportDataFinished(int result) + { + switch (result) + { + case ImportDataTask.SUCCESS: + screen.refresh(null); + screen.showMessage(R.string.habits_imported); + break; + + case ImportDataTask.NOT_RECOGNIZED: + screen.showMessage(R.string.file_not_recognized); + break; + + default: + screen.showMessage(R.string.could_not_import); + break; + } + } + + public void exportCSV() + { + ExportCSVTask task = new ExportCSVTask(Habit.getAll(true), screen.getProgressBar()); + task.setListener(this); + task.execute(); + } + + @Override + public void onExportCSVFinished(String filename) + { + if(filename != null) sys.sendFile(filename); + else screen.showMessage(R.string.could_not_export); + } + + public void exportDB() + { + ExportDBTask task = new ExportDBTask(screen.getProgressBar()); + task.setListener(this); + task.execute(); + } + + @Override + public void onExportDBFinished(String filename) + { + if(filename != null) sys.sendFile(filename); + else screen.showMessage(R.string.could_not_export); + } + + public void sendBugReport() + { + try + { + sys.dumpBugReportToFile(); + } + catch (IOException e) + { + // ignored + } + + try + { + String log = "---------- BUG REPORT BEGINS ----------\n"; + log += sys.getBugReport(); + log += "---------- BUG REPORT ENDS ------------\n"; + String to = "dev@loophabits.org"; + String subject = "Bug Report - Loop Habit Tracker"; + sys.sendEmail(log, to, subject); + } + catch (IOException e) + { + e.printStackTrace(); + screen.showMessage(R.string.bug_report_failed); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/Preferences.java b/app/src/main/java/org/isoron/uhabits/Preferences.java new file mode 100644 index 000000000..f7b8f4f26 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/Preferences.java @@ -0,0 +1,81 @@ +/* + * 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; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public class Preferences +{ + private static Preferences singleton; + + private Context context; + private SharedPreferences prefs; + + private Preferences() + { + this.context = HabitsApplication.getContext(); + prefs = PreferenceManager.getDefaultSharedPreferences(context); + } + + public static Preferences getInstance() + { + if(singleton == null) singleton = new Preferences(); + return singleton; + } + + public void initialize() + { + PreferenceManager.setDefaultValues(context, R.xml.preferences, false); + } + + + public void incrementLaunchCount() + { + int count = prefs.getInt("launch_count", 0); + prefs.edit().putInt("launch_count", count + 1).apply(); + } + + public void updateLastAppVersion() + { + prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply(); + } + + public boolean isFirstRun() + { + return prefs.getBoolean("pref_first_run", true); + } + + public void setFirstRun(boolean isFirstRun) + { + prefs.edit().putBoolean("pref_first_run", isFirstRun).apply(); + } + + public void setLastHintTimestamp(long timestamp) + { + prefs.edit().putLong("last_hint_timestamp", timestamp).apply(); + } + + public boolean isShortToggleEnabled() + { + return prefs.getBoolean("pref_short_toggle", false); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java index ccea2b14b..1eb23a12a 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java @@ -20,12 +20,10 @@ package org.isoron.uhabits.tasks; import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ProgressBar; -import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.FileUtils; import java.io.File; import java.io.IOException; @@ -58,12 +56,7 @@ public class ExportCSVTask extends BaseTask protected void onPreExecute() { super.onPreExecute(); - - if(progressBar != null) - { - progressBar.setIndeterminate(true); - progressBar.setVisibility(View.VISIBLE); - } + if(progressBar != null) progressBar.show(); } @Override @@ -72,9 +65,7 @@ public class ExportCSVTask extends BaseTask if(listener != null) listener.onExportCSVFinished(archiveFilename); - if(progressBar != null) - progressBar.setVisibility(View.GONE); - + if(progressBar != null) progressBar.hide(); super.onPostExecute(null); } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java index 60b3cba19..541d3519f 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java @@ -20,8 +20,6 @@ package org.isoron.uhabits.tasks; import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ProgressBar; import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.utils.FileUtils; @@ -54,23 +52,14 @@ public class ExportDBTask extends BaseTask protected void onPreExecute() { super.onPreExecute(); - - if(progressBar != null) - { - progressBar.setIndeterminate(true); - progressBar.setVisibility(View.VISIBLE); - } + if(progressBar != null) progressBar.show(); } @Override protected void onPostExecute(Void aVoid) { - if(listener != null) - listener.onExportDBFinished(filename); - - if(progressBar != null) - progressBar.setVisibility(View.GONE); - + if(listener != null) listener.onExportDBFinished(filename); + if(progressBar != null) progressBar.hide(); super.onPostExecute(null); } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java index 477f1f51b..cdc6c538a 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java @@ -21,8 +21,6 @@ package org.isoron.uhabits.tasks; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.view.View; -import android.widget.ProgressBar; import org.isoron.uhabits.io.GenericImporter; @@ -36,7 +34,7 @@ public class ImportDataTask extends BaseTask public interface Listener { - void onImportFinished(int result); + void onImportDataFinished(int result); } @Nullable @@ -66,21 +64,14 @@ public class ImportDataTask extends BaseTask { super.onPreExecute(); - if(progressBar != null) - { - progressBar.setIndeterminate(true); - progressBar.setVisibility(View.VISIBLE); - } + if(progressBar != null) progressBar.show(); } @Override protected void onPostExecute(Void aVoid) { - if(progressBar != null) - progressBar.setVisibility(View.GONE); - - if(listener != null) listener.onImportFinished(result); - + if(progressBar != null) progressBar.hide(); + if(listener != null) listener.onImportDataFinished(result); super.onPostExecute(null); } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java b/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java new file mode 100644 index 000000000..17c3ebbf1 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java @@ -0,0 +1,26 @@ +/* + * 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.tasks; + +public interface ProgressBar +{ + void show(); + void hide(); +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java b/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java new file mode 100644 index 000000000..c366bf88c --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java @@ -0,0 +1,47 @@ +/* + * 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.ui; + +import android.view.View; + +import org.isoron.uhabits.tasks.ProgressBar; + +public class AndroidProgressBar implements ProgressBar +{ + private final android.widget.ProgressBar progressBar; + + public AndroidProgressBar(android.widget.ProgressBar progressBar) + { + this.progressBar = progressBar; + } + + @Override + public void show() + { + progressBar.setIndeterminate(true); + progressBar.setVisibility(View.VISIBLE); + } + + @Override + public void hide() + { + progressBar.setVisibility(View.GONE); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java index f41b6eb87..d8d6f26e9 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java @@ -72,28 +72,28 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U { if (undoList.isEmpty()) { - showToast(R.string.toast_nothing_to_undo); + showMessage(R.string.toast_nothing_to_undo); return; } Command last = undoList.pop(); redoList.push(last); last.undo(); - showToast(last.getUndoStringId()); + showMessage(last.getUndoStringId()); } protected void redo() { if (redoList.isEmpty()) { - showToast(R.string.toast_nothing_to_redo); + showMessage(R.string.toast_nothing_to_redo); return; } Command last = redoList.pop(); executeCommand(last, false, null); } - public void showToast(Integer stringId) + public void showMessage(Integer stringId) { if (stringId == null) return; if (toast == null) toast = Toast.makeText(this, stringId, Toast.LENGTH_SHORT); @@ -126,7 +126,7 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U }.execute(); - showToast(command.getExecuteStringId()); + showMessage(command.getExecuteStringId()); } protected void setupSupportActionBar(boolean homeButtonEnabled) @@ -156,7 +156,7 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U try { ex.printStackTrace(); - HabitsApplication.dumpBugReportToFile(); + ((HabitsApplication) getApplication()).dumpBugReportToFile(); } catch(Exception e) { diff --git a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsController.java new file mode 100644 index 000000000..dd56c4622 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsController.java @@ -0,0 +1,37 @@ +/* + * 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.ui.list; + +public class ListHabitsController +{ + public interface Screen + { + + } + + private Screen screen; + + + public void setScreen(Screen screen) + { + this.screen = screen; + } + +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java index 4b9bf23d1..111246108 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java @@ -20,16 +20,9 @@ package org.isoron.uhabits.ui.list; import android.app.Activity; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.view.ActionMode; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.Menu; @@ -40,7 +33,6 @@ import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.OnItemClickListener; import android.widget.LinearLayout; import android.widget.ProgressBar; @@ -48,80 +40,96 @@ import android.widget.TextView; import com.mobeta.android.dslv.DragSortController; import com.mobeta.android.dslv.DragSortListView; -import com.mobeta.android.dslv.DragSortListView.DropListener; -import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.Preferences; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.ToggleRepetitionCommand; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.HintManager; import org.isoron.uhabits.ui.edit.EditHabitDialogFragment; -import org.isoron.uhabits.ui.settings.FilePickerDialog; -import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.ui.HintManager; -import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.utils.InterfaceUtils.OnSavedListener; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.ExportCSVTask; -import org.isoron.uhabits.tasks.ExportDBTask; -import org.isoron.uhabits.tasks.ImportDataTask; +import org.isoron.uhabits.utils.ReminderUtils; -import java.io.File; import java.util.Date; import java.util.LinkedList; import java.util.List; public class ListHabitsFragment extends Fragment - implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener, + implements OnSavedListener, OnItemClickListener, OnLongClickListener, OnClickListener, ListHabitsLoader.Listener, AdapterView.OnItemLongClickListener, - HabitSelectionCallback.Listener, ImportDataTask.Listener, ExportCSVTask.Listener, - ExportDBTask.Listener + HabitSelectionCallback.Listener, ListHabitsController.Screen { long lastLongClick = 0; - private boolean isShortToggleEnabled; private boolean showArchived; private ActionMode actionMode; private ListHabitsAdapter adapter; - private ListHabitsLoader loader; + public ListHabitsLoader loader; private HintManager hintManager; private ListHabitsHelper helper; private List selectedPositions; private OnHabitClickListener habitClickListener; private BaseActivity activity; - private SharedPreferences prefs; private DragSortListView listView; private LinearLayout llButtonsHeader; - private ProgressBar progressBar; + public ProgressBar progressBar; private View llEmpty; + private ListHabitsController controller; + private Preferences prefs; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.list_habits_fragment, container, false); + View llHint = view.findViewById(R.id.llHint); - TextView tvStarEmpty = (TextView) view.findViewById(R.id.tvStarEmpty); - listView = (DragSortListView) view.findViewById(R.id.listView); llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader); llEmpty = view.findViewById(R.id.llEmpty); - progressBar = (ProgressBar) view.findViewById(R.id.progressBar); progressBar.setVisibility(View.GONE); selectedPositions = new LinkedList<>(); loader = new ListHabitsLoader(); helper = new ListHabitsHelper(activity, loader); + hintManager = new HintManager(activity, llHint); loader.setListener(this); loader.setCheckmarkCount(helper.getButtonCount()); llHint.setOnClickListener(this); + + TextView tvStarEmpty = (TextView) view.findViewById(R.id.tvStarEmpty); tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(activity)); + createListView(view); + + if(savedInstanceState != null) + { + EditHabitDialogFragment frag = (EditHabitDialogFragment) getFragmentManager() + .findFragmentByTag("editHabit"); + if(frag != null) frag.setOnSavedListener(this); + } + + loader.updateAllHabits(true); + + controller = new ListHabitsController(); + controller.setScreen(this); + prefs = Preferences.getInstance(); + + setHasOptionsMenu(true); + return view; + } + + private void createListView(View view) + { adapter = new ListHabitsAdapter(getActivity(), loader); adapter.setSelectedPositions(selectedPositions); adapter.setOnCheckmarkClickListener(this); @@ -130,26 +138,15 @@ public class ListHabitsFragment extends Fragment DragSortListView.DragListener dragListener = new HabitsDragListener(); DragSortController dragSortController = new HabitsDragSortController(); + listView = (DragSortListView) view.findViewById(R.id.listView); listView.setAdapter(adapter); listView.setOnItemClickListener(this); listView.setOnItemLongClickListener(this); - listView.setDropListener(this); + listView.setDropListener(new HabitsDropListener()); listView.setDragListener(dragListener); listView.setFloatViewManager(dragSortController); listView.setDragEnabled(true); listView.setLongClickable(true); - - if(savedInstanceState != null) - { - EditHabitDialogFragment frag = (EditHabitDialogFragment) getFragmentManager() - .findFragmentByTag("editHabit"); - if(frag != null) frag.setOnSavedListener(this); - } - - loader.updateAllHabits(true); - - setHasOptionsMenu(true); - return view; } @Override @@ -158,9 +155,7 @@ public class ListHabitsFragment extends Fragment { super.onAttach(activity); this.activity = (BaseActivity) activity; - habitClickListener = (OnHabitClickListener) activity; - prefs = PreferenceManager.getDefaultSharedPreferences(activity); } @Override @@ -175,9 +170,7 @@ public class ListHabitsFragment extends Fragment helper.updateEmptyMessage(llEmpty); helper.updateHeader(llButtonsHeader); hintManager.showHintIfAppropriate(); - adapter.notifyDataSetChanged(); - isShortToggleEnabled = prefs.getBoolean("pref_short_toggle", false); } @Override @@ -192,51 +185,43 @@ public class ListHabitsFragment extends Fragment { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.list_habits_options, menu); - MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived); showArchivedItem.setChecked(showArchived); } - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) - { - super.onCreateContextMenu(menu, view, menuInfo); - getActivity().getMenuInflater().inflate(R.menu.list_habits_context, menu); - - AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - final Habit habit = loader.habits.get(info.id); - - if (habit.isArchived()) menu.findItem(R.id.action_archive_habit).setVisible(false); - else menu.findItem(R.id.action_unarchive_habit).setVisible(false); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_add: - { - EditHabitDialogFragment frag = EditHabitDialogFragment.createHabitFragment(); - frag.setOnSavedListener(this); - frag.show(getFragmentManager(), "editHabit"); + showCreateHabitScreen(); return true; - } case R.id.action_show_archived: - { - showArchived = !showArchived; - loader.setIncludeArchived(showArchived); - loader.updateAllHabits(true); - activity.invalidateOptionsMenu(); + toggleShowArchived(); return true; - } default: return super.onOptionsItemSelected(item); } } + private void toggleShowArchived() + { + showArchived = !showArchived; + loader.setIncludeArchived(showArchived); + loader.updateAllHabits(true); + activity.invalidateOptionsMenu(); + } + + private void showCreateHabitScreen() + { + EditHabitDialogFragment frag = EditHabitDialogFragment.createHabitFragment(); + frag.setOnSavedListener(this); + frag.show(getFragmentManager(), "editHabit"); + } + @Override public void onItemClick(AdapterView parent, View view, int position, long id) { @@ -249,44 +234,43 @@ public class ListHabitsFragment extends Fragment } else { - int k = selectedPositions.indexOf(position); - if(k < 0) - selectedPositions.add(position); - else - selectedPositions.remove(k); - - if(selectedPositions.isEmpty()) actionMode.finish(); - else actionMode.invalidate(); - + toggleItemSelected(position); adapter.notifyDataSetChanged(); } } + private void toggleItemSelected(int position) + { + int k = selectedPositions.indexOf(position); + if(k < 0) selectedPositions.add(position); + else selectedPositions.remove(k); + + if(selectedPositions.isEmpty()) actionMode.finish(); + else actionMode.invalidate(); + } + @Override public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - selectItem(position); + selectHabit(position); return true; } - private void selectItem(int position) + private void selectHabit(int position) { - if(!selectedPositions.contains(position)) - selectedPositions.add(position); - + if(!selectedPositions.contains(position)) selectedPositions.add(position); adapter.notifyDataSetChanged(); + if(actionMode == null) startSupportActionMode(); + actionMode.invalidate(); + } - if(actionMode == null) - { - HabitSelectionCallback callback = new HabitSelectionCallback(activity, loader); - callback.setSelectedPositions(selectedPositions); - callback.setOnSavedListener(this); - callback.setListener(this); - - actionMode = activity.startSupportActionMode(callback); - } - - if(actionMode != null) actionMode.invalidate(); + private void startSupportActionMode() + { + HabitSelectionCallback callback = new HabitSelectionCallback(activity, loader); + callback.setSelectedPositions(selectedPositions); + callback.setOnSavedListener(this); + callback.setListener(this); + actionMode = activity.startSupportActionMode(callback); } @Override @@ -320,12 +304,11 @@ public class ListHabitsFragment extends Fragment private void onCheckmarkLongClick(View v) { - if (isShortToggleEnabled) return; - - toggleCheck(v); + if (prefs.isShortToggleEnabled()) return; + toggleCheckmark(v); } - private void toggleCheck(View v) + private void toggleCheckmark(View v) { Long id = helper.getHabitIdFromCheckmarkView(v); Habit habit = loader.habits.get(id); @@ -347,25 +330,14 @@ public class ListHabitsFragment extends Fragment activity.executeCommand(c, refreshKey); } - @Override - public void drop(int from, int to) - { - if(from == to) return; - if(actionMode != null) actionMode.finish(); - - loader.reorder(from, to); - adapter.notifyDataSetChanged(); - loader.updateAllHabits(false); - } - @Override public void onClick(View v) { switch (v.getId()) { case R.id.tvCheck: - if (isShortToggleEnabled) toggleCheck(v); - else activity.showToast(R.string.long_press_to_toggle); + if (prefs.isShortToggleEnabled()) toggleCheckmark(v); + else activity.showMessage(R.string.long_press_to_toggle); break; case R.id.llHint: @@ -414,109 +386,31 @@ public class ListHabitsFragment extends Fragment } } - private class HabitsDragListener implements DragSortListView.DragListener + private class HabitsDropListener implements DragSortListView.DropListener { @Override - public void drag(int from, int to) - { - } - - @Override - public void startDrag(int position) + public void drop(int from, int to) { - selectItem(position); - } - } - - public void showImportDialog() - { - File dir = FileUtils.getFilesDir(null); - if(dir == null) - { - activity.showToast(R.string.could_not_import); - return; - } + if(from == to) return; + if(actionMode != null) actionMode.finish(); - FilePickerDialog picker = new FilePickerDialog(activity, dir); - picker.setListener(new FilePickerDialog.OnFileSelectedListener() - { - @Override - public void onFileSelected(File file) - { - ImportDataTask task = new ImportDataTask(file, progressBar); - task.setListener(ListHabitsFragment.this); - task.execute(); - } - }); - - picker.show(); - } - - @Override - public void onImportFinished(int result) - { - switch (result) - { - case ImportDataTask.SUCCESS: - loader.updateAllHabits(true); - activity.showToast(R.string.habits_imported); - break; - - case ImportDataTask.NOT_RECOGNIZED: - activity.showToast(R.string.file_not_recognized); - break; - - default: - activity.showToast(R.string.could_not_import); - break; + loader.reorder(from, to); + adapter.notifyDataSetChanged(); + loader.updateAllHabits(false); } } - public void exportAllHabits() - { - ExportCSVTask task = new ExportCSVTask(Habit.getAll(true), progressBar); - task.setListener(this); - task.execute(); - } - - @Override - public void onExportCSVFinished(@Nullable String archiveFilename) + private class HabitsDragListener implements DragSortListView.DragListener { - if(archiveFilename != null) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); - activity.startActivity(intent); - } - else + @Override + public void drag(int from, int to) { - activity.showToast(R.string.could_not_export); } - } - - public void exportDB() - { - ExportDBTask task = new ExportDBTask(progressBar); - task.setListener(this); - task.execute(); - } - @Override - public void onExportDBFinished(@Nullable String filename) - { - if(filename != null) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/octet-stream"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename))); - activity.startActivity(intent); - } - else + @Override + public void startDrag(int position) { - activity.showToast(R.string.could_not_export); + selectHabit(position); } } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java index 9789b6790..bdb925c12 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java @@ -27,7 +27,7 @@ import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceFragmentCompat; -import org.isoron.uhabits.MainActivity; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.utils.InterfaceUtils; @@ -43,10 +43,10 @@ public class SettingsFragment extends PreferenceFragmentCompat super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); - setResultOnPreferenceClick("importData", MainActivity.RESULT_IMPORT_DATA); - setResultOnPreferenceClick("exportCSV", MainActivity.RESULT_EXPORT_CSV); - setResultOnPreferenceClick("exportDB", MainActivity.RESULT_EXPORT_DB); - setResultOnPreferenceClick("bugReport", MainActivity.RESULT_BUG_REPORT); + setResultOnPreferenceClick("importData", HabitsApplication.RESULT_IMPORT_DATA); + setResultOnPreferenceClick("exportCSV", HabitsApplication.RESULT_EXPORT_CSV); + setResultOnPreferenceClick("exportDB", HabitsApplication.RESULT_EXPORT_DB); + setResultOnPreferenceClick("bugReport", HabitsApplication.RESULT_BUG_REPORT); updateRingtoneDescription(); diff --git a/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java index 5625bd7f9..f6492725a 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java @@ -33,10 +33,7 @@ import android.preference.PreferenceManager; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.TypedValue; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import org.isoron.uhabits.BuildConfig; import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; @@ -71,32 +68,6 @@ public abstract class InterfaceUtils return fontAwesome; } - public static void showSoftKeyboard(View view) - { - InputMethodManager imm = (InputMethodManager) view.getContext() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); - } - - public static void incrementLaunchCount(Context context) - { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - int count = prefs.getInt("launch_count", 0); - prefs.edit().putInt("launch_count", count + 1).apply(); - } - - public static void updateLastAppVersion(Context context) - { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply(); - } - - public static int getLaunchCount(Context context) - { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - return prefs.getInt("launch_count", 0); - } - public static String getAttribute(Context context, AttributeSet attrs, String name, String defaultValue) { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java index 9d4f187c3..6082f0a2b 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java @@ -30,7 +30,6 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; -import org.isoron.uhabits.MainActivity; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; @@ -81,7 +80,7 @@ public class HabitPickerDialog extends Activity implements AdapterView.OnItemCli getApplicationContext()); prefs.edit().putLong(BaseWidgetProvider.getHabitIdKey(widgetId), habitId).commit(); - MainActivity.updateWidgets(this); + WidgetManager.updateWidgets(this); Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); diff --git a/app/src/main/java/org/isoron/uhabits/widgets/WidgetManager.java b/app/src/main/java/org/isoron/uhabits/widgets/WidgetManager.java new file mode 100644 index 000000000..69643059c --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/widgets/WidgetManager.java @@ -0,0 +1,47 @@ +/* + * 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.widgets; + +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; + +public class WidgetManager +{ + public static void updateWidgets(Context context) + { + updateWidgets(context, CheckmarkWidgetProvider.class); + updateWidgets(context, HistoryWidgetProvider.class); + updateWidgets(context, ScoreWidgetProvider.class); + updateWidgets(context, StreakWidgetProvider.class); + updateWidgets(context, FrequencyWidgetProvider.class); + } + + private static void updateWidgets(Context context, Class providerClass) + { + ComponentName provider = new ComponentName(context, providerClass); + Intent intent = new Intent(context, providerClass); + intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + int ids[] = AppWidgetManager.getInstance(context).getAppWidgetIds(provider); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids); + context.sendBroadcast(intent); + } +} From e0198e9926a64d5e78e95c7b31ba6a1b4c7d92fe Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 27 May 2016 10:20:16 -0400 Subject: [PATCH 004/184] Reorganize files --- app/src/main/AndroidManifest.xml | 6 +++--- .../java/org/isoron/uhabits/HabitBroadcastReceiver.java | 2 +- app/src/main/java/org/isoron/uhabits/MainActivity.java | 8 ++++---- app/src/main/java/org/isoron/uhabits/MainController.java | 1 + .../org/isoron/uhabits/ui/{ => about}/AboutActivity.java | 3 ++- .../ui/{ => habits}/edit/EditHabitDialogFragment.java | 2 +- .../uhabits/ui/{ => habits}/edit/HistoryEditorDialog.java | 2 +- .../uhabits/ui/{ => habits}/edit/WeekdayPickerDialog.java | 2 +- .../ui/{ => habits}/list/HabitSelectionCallback.java | 4 ++-- .../uhabits/ui/{ => habits}/list/ListHabitsAdapter.java | 2 +- .../ui/{ => habits}/list/ListHabitsController.java | 2 +- .../uhabits/ui/{ => habits}/list/ListHabitsFragment.java | 8 ++++---- .../uhabits/ui/{ => habits}/list/ListHabitsHelper.java | 2 +- .../uhabits/ui/{ => habits}/list/ListHabitsLoader.java | 2 +- .../uhabits/ui/{ => habits}/show/ShowHabitActivity.java | 2 +- .../uhabits/ui/{ => habits}/show/ShowHabitFragment.java | 6 +++--- .../org/isoron/uhabits/ui/{ => intro}/IntroActivity.java | 2 +- .../java/org/isoron/uhabits/{ => utils}/Preferences.java | 6 +++++- app/src/main/res/layout/edit_habit.xml | 2 +- app/src/main/res/layout/list_habits_activity.xml | 2 +- app/src/main/res/layout/show_habit.xml | 2 +- app/src/main/res/layout/show_habit_activity.xml | 4 ++-- 22 files changed, 39 insertions(+), 33 deletions(-) rename app/src/main/java/org/isoron/uhabits/ui/{ => about}/AboutActivity.java (97%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/edit/EditHabitDialogFragment.java (99%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/edit/HistoryEditorDialog.java (98%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/edit/WeekdayPickerDialog.java (98%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/list/HabitSelectionCallback.java (98%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/list/ListHabitsAdapter.java (98%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/list/ListHabitsController.java (95%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/list/ListHabitsFragment.java (98%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/list/ListHabitsHelper.java (99%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/list/ListHabitsLoader.java (99%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/show/ShowHabitActivity.java (97%) rename app/src/main/java/org/isoron/uhabits/ui/{ => habits}/show/ShowHabitFragment.java (98%) rename app/src/main/java/org/isoron/uhabits/ui/{ => intro}/IntroActivity.java (98%) rename app/src/main/java/org/isoron/uhabits/{ => utils}/Preferences.java (93%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4a764c2b5..055cae918 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,7 +60,7 @@ @@ -89,7 +89,7 @@ . */ -package org.isoron.uhabits.ui; +package org.isoron.uhabits.ui.about; import android.content.Intent; import android.net.Uri; @@ -27,6 +27,7 @@ import android.widget.TextView; import org.isoron.uhabits.BuildConfig; import org.isoron.uhabits.R; +import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.utils.InterfaceUtils; public class AboutActivity extends BaseActivity implements View.OnClickListener diff --git a/app/src/main/java/org/isoron/uhabits/ui/edit/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java similarity index 99% rename from app/src/main/java/org/isoron/uhabits/ui/edit/EditHabitDialogFragment.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java index 1a21a8c9c..b70b2ce2b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/edit/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.edit; +package org.isoron.uhabits.ui.habits.edit; import android.annotation.SuppressLint; import android.content.SharedPreferences; diff --git a/app/src/main/java/org/isoron/uhabits/ui/edit/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/edit/HistoryEditorDialog.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java index d43bcfcbf..147d62f63 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/edit/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.edit; +package org.isoron.uhabits.ui.habits.edit; import android.app.Dialog; import android.content.Context; diff --git a/app/src/main/java/org/isoron/uhabits/ui/edit/WeekdayPickerDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/edit/WeekdayPickerDialog.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java index e2de134d1..b6af16b01 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/edit/WeekdayPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.edit; +package org.isoron.uhabits.ui.habits.edit; import android.app.Dialog; import android.content.DialogInterface; diff --git a/app/src/main/java/org/isoron/uhabits/ui/list/HabitSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitSelectionCallback.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/list/HabitSelectionCallback.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitSelectionCallback.java index a065054b7..3617c15af 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/list/HabitSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitSelectionCallback.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.list; +package org.isoron.uhabits.ui.habits.list; import android.content.DialogInterface; import android.support.v7.app.AlertDialog; @@ -35,7 +35,7 @@ import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.ui.edit.EditHabitDialogFragment; +import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.InterfaceUtils; diff --git a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsAdapter.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsAdapter.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsAdapter.java index dd0bf2810..223b7d471 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsAdapter.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.list; +package org.isoron.uhabits.ui.habits.list; import android.content.Context; import android.view.LayoutInflater; diff --git a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java similarity index 95% rename from app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsController.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java index dd56c4622..d6d125bde 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.list; +package org.isoron.uhabits.ui.habits.list; public class ListHabitsController { diff --git a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java index 111246108..43fe40eda 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.list; +package org.isoron.uhabits.ui.habits.list; import android.app.Activity; import android.os.Bundle; @@ -41,14 +41,14 @@ import android.widget.TextView; import com.mobeta.android.dslv.DragSortController; import com.mobeta.android.dslv.DragSortListView; -import org.isoron.uhabits.Preferences; +import org.isoron.uhabits.utils.Preferences; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.ui.HintManager; -import org.isoron.uhabits.ui.edit.EditHabitDialogFragment; +import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.utils.InterfaceUtils.OnSavedListener; @@ -130,6 +130,7 @@ public class ListHabitsFragment extends Fragment private void createListView(View view) { + listView = (DragSortListView) view.findViewById(R.id.listView); adapter = new ListHabitsAdapter(getActivity(), loader); adapter.setSelectedPositions(selectedPositions); adapter.setOnCheckmarkClickListener(this); @@ -138,7 +139,6 @@ public class ListHabitsFragment extends Fragment DragSortListView.DragListener dragListener = new HabitsDragListener(); DragSortController dragSortController = new HabitsDragSortController(); - listView = (DragSortListView) view.findViewById(R.id.listView); listView.setAdapter(adapter); listView.setOnItemClickListener(this); listView.setOnItemLongClickListener(this); diff --git a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java similarity index 99% rename from app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsHelper.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java index 71411f383..3c74153ef 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.list; +package org.isoron.uhabits.ui.habits.list; import android.content.Context; import android.content.SharedPreferences; diff --git a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsLoader.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsLoader.java similarity index 99% rename from app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsLoader.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsLoader.java index baed5b504..12f2331ab 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/list/ListHabitsLoader.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsLoader.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.list; +package org.isoron.uhabits.ui.habits.list; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; diff --git a/app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java similarity index 97% rename from app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitActivity.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java index 38a6d3d8e..a34a61fe4 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.show; +package org.isoron.uhabits.ui.habits.show; import android.content.ContentUris; import android.net.Uri; diff --git a/app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitFragment.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 68873a6e5..630d47fcb 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.show; +package org.isoron.uhabits.ui.habits.show; import android.os.Bundle; import android.support.annotation.Nullable; @@ -36,8 +36,8 @@ import android.widget.TextView; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.ui.edit.EditHabitDialogFragment; -import org.isoron.uhabits.ui.edit.HistoryEditorDialog; +import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; +import org.isoron.uhabits.ui.habits.edit.HistoryEditorDialog; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.ReminderUtils; diff --git a/app/src/main/java/org/isoron/uhabits/ui/IntroActivity.java b/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/IntroActivity.java rename to app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java index f58298186..1b0229633 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/IntroActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui; +package org.isoron.uhabits.ui.intro; import android.graphics.Color; import android.os.Bundle; diff --git a/app/src/main/java/org/isoron/uhabits/Preferences.java b/app/src/main/java/org/isoron/uhabits/utils/Preferences.java similarity index 93% rename from app/src/main/java/org/isoron/uhabits/Preferences.java rename to app/src/main/java/org/isoron/uhabits/utils/Preferences.java index f7b8f4f26..d9d216676 100644 --- a/app/src/main/java/org/isoron/uhabits/Preferences.java +++ b/app/src/main/java/org/isoron/uhabits/utils/Preferences.java @@ -17,12 +17,16 @@ * with this program. If not, see . */ -package org.isoron.uhabits; +package org.isoron.uhabits.utils; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import org.isoron.uhabits.BuildConfig; +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.R; + public class Preferences { private static Preferences singleton; diff --git a/app/src/main/res/layout/edit_habit.xml b/app/src/main/res/layout/edit_habit.xml index cbca604bf..0643359de 100644 --- a/app/src/main/res/layout/edit_habit.xml +++ b/app/src/main/res/layout/edit_habit.xml @@ -22,7 +22,7 @@ style="@style/dialogForm" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - tools:context=".ui.edit.EditHabitDialogFragment" + tools:context=".ui.habits.edit.EditHabitDialogFragment" tools:ignore="MergeRootFrame"> + tools:context=".ui.habits.show.ShowHabitActivity"> @@ -35,7 +35,7 @@ Date: Fri, 27 May 2016 12:19:29 -0400 Subject: [PATCH 005/184] Create HabitListView and move most code from Fragment to it --- .../java/org/isoron/uhabits/MainActivity.java | 10 +- ...bitsAdapter.java => HabitListAdapter.java} | 6 +- ...HabitsLoader.java => HabitListLoader.java} | 14 +- ...k.java => HabitListSelectionCallback.java} | 8 +- .../uhabits/ui/habits/list/HabitListView.java | 273 ++++++++++++++++++ .../ui/habits/list/ListHabitsController.java | 1 - .../ui/habits/list/ListHabitsFragment.java | 272 ++++------------- .../ui/habits/list/ListHabitsHelper.java | 4 +- .../ui/habits/show/ShowHabitFragment.java | 2 +- .../main/res/layout/list_habits_fragment.xml | 3 +- ...s_options.xml => list_habits_fragment.xml} | 14 + ..._context.xml => list_habits_selection.xml} | 0 ...list_habits_menu.xml => main_activity.xml} | 14 +- ...gment_menu.xml => show_habit_fragment.xml} | 0 14 files changed, 373 insertions(+), 248 deletions(-) rename app/src/main/java/org/isoron/uhabits/ui/habits/list/{ListHabitsAdapter.java => HabitListAdapter.java} (94%) rename app/src/main/java/org/isoron/uhabits/ui/habits/list/{ListHabitsLoader.java => HabitListLoader.java} (96%) rename app/src/main/java/org/isoron/uhabits/ui/habits/list/{HabitSelectionCallback.java => HabitListSelectionCallback.java} (96%) create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java rename app/src/main/res/menu/{list_habits_options.xml => list_habits_fragment.xml} (71%) rename app/src/main/res/menu/{list_habits_context.xml => list_habits_selection.xml} (100%) rename app/src/main/res/menu/{list_habits_menu.xml => main_activity.xml} (82%) rename app/src/main/res/menu/{show_habit_fragment_menu.xml => show_habit_fragment.xml} (100%) diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 43daf75ee..8d425572a 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -50,7 +50,7 @@ import org.isoron.uhabits.widgets.WidgetManager; import java.io.File; public class MainActivity extends BaseActivity - implements ListHabitsFragment.OnHabitClickListener, MainController.Screen + implements ListHabitsFragment.Listener, MainController.Screen { private MainController controller; private ListHabitsFragment listHabitsFragment; @@ -88,7 +88,7 @@ public class MainActivity extends BaseActivity public boolean onCreateOptionsMenu(Menu menu) { menu.clear(); - getMenuInflater().inflate(R.menu.list_habits_menu, menu); + getMenuInflater().inflate(R.menu.main_activity, menu); MenuItem nightModeItem = menu.findItem(R.id.action_night_mode); nightModeItem.setChecked(InterfaceUtils.isNightMode()); return true; @@ -191,7 +191,7 @@ public class MainActivity extends BaseActivity } @Override - public void onHabitClicked(Habit habit) + public void onHabitClick(Habit habit) { showHabitScreen(habit); } @@ -258,12 +258,12 @@ public class MainActivity extends BaseActivity @Override public void refresh(Long refreshKey) { - listHabitsFragment.loader.updateAllHabits(true); + listHabitsFragment.refresh(refreshKey); } @Override public ProgressBar getProgressBar() { - return new AndroidProgressBar(listHabitsFragment.progressBar); + return new AndroidProgressBar(listHabitsFragment.getProgressBar()); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java similarity index 94% rename from app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsAdapter.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java index 223b7d471..e95aa3d11 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java @@ -31,16 +31,16 @@ import org.isoron.uhabits.models.Habit; import java.util.List; -class ListHabitsAdapter extends BaseAdapter +class HabitListAdapter extends BaseAdapter { private LayoutInflater inflater; - private ListHabitsLoader loader; + private HabitListLoader loader; private ListHabitsHelper helper; private List selectedPositions; private View.OnLongClickListener onCheckmarkLongClickListener; private View.OnClickListener onCheckmarkClickListener; - public ListHabitsAdapter(Context context, ListHabitsLoader loader) + public HabitListAdapter(Context context, HabitListLoader loader) { this.loader = loader; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsLoader.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsLoader.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java index 12f2331ab..496d336d4 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsLoader.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java @@ -19,6 +19,8 @@ package org.isoron.uhabits.ui.habits.list; +import android.support.annotation.Nullable; + import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; @@ -26,7 +28,7 @@ import org.isoron.uhabits.tasks.BaseTask; import java.util.HashMap; import java.util.List; -public class ListHabitsLoader +public class HabitListLoader { public interface Listener { @@ -36,6 +38,7 @@ public class ListHabitsLoader private BaseTask currentFetchTask; private int checkmarkCount; + @Nullable private Listener listener; private Long lastLoadTimestamp; @@ -56,7 +59,7 @@ public class ListHabitsLoader this.checkmarkCount = checkmarkCount; } - public void setListener(Listener listener) + public void setListener(@Nullable Listener listener) { this.listener = listener; } @@ -66,7 +69,7 @@ public class ListHabitsLoader return lastLoadTimestamp; } - public ListHabitsLoader() + public HabitListLoader() { habits = new HashMap<>(); checkmarks = new HashMap<>(); @@ -164,7 +167,6 @@ public class ListHabitsLoader currentFetchTask = null; if(listener != null) listener.onLoadFinished(); - super.onPostExecute(null); } @@ -194,9 +196,7 @@ public class ListHabitsLoader @Override protected void onPostExecute(Void aVoid) { - if(listener != null) - listener.onLoadFinished(); - + if(listener != null) listener.onLoadFinished(); super.onPostExecute(null); } }.execute(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitSelectionCallback.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java index 3617c15af..41378e8d0 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java @@ -42,9 +42,9 @@ import org.isoron.uhabits.utils.InterfaceUtils; import java.util.LinkedList; import java.util.List; -public class HabitSelectionCallback implements ActionMode.Callback +public class HabitListSelectionCallback implements ActionMode.Callback { - private ListHabitsLoader loader; + private HabitListLoader loader; private List selectedPositions; private BaseActivity activity; private Listener listener; @@ -55,7 +55,7 @@ public class HabitSelectionCallback implements ActionMode.Callback void onActionModeDestroyed(ActionMode mode); } - public HabitSelectionCallback(BaseActivity activity, ListHabitsLoader loader) + public HabitListSelectionCallback(BaseActivity activity, HabitListLoader loader) { this.activity = activity; this.loader = loader; @@ -80,7 +80,7 @@ public class HabitSelectionCallback implements ActionMode.Callback @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { - activity.getMenuInflater().inflate(R.menu.list_habits_context, menu); + activity.getMenuInflater().inflate(R.menu.list_habits_selection, menu); updateTitle(mode); updateActions(menu); return true; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java new file mode 100644 index 000000000..606ae3527 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java @@ -0,0 +1,273 @@ +/* + * 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.ui.habits.list; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.HapticFeedbackConstants; +import android.view.View; +import android.widget.AdapterView; + +import com.mobeta.android.dslv.DragSortController; +import com.mobeta.android.dslv.DragSortListView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.Preferences; + +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class HabitListView extends DragSortListView implements View.OnClickListener, + View.OnLongClickListener, DragSortListView.DropListener, AdapterView.OnItemClickListener, + AdapterView.OnItemLongClickListener, DragSortListView.DragListener, HabitListLoader.Listener +{ + private final HabitListLoader loader; + private final HabitListAdapter adapter; + private final ListHabitsHelper helper; + private final Preferences prefs; + private final List selectedPositions; + + @Nullable + private Listener listener; + private long lastLongClick; + private boolean showArchived; + + public HabitListView(Context context, AttributeSet attrs) + { + super(context, attrs); + + loader = new HabitListLoader(); + adapter = new HabitListAdapter(context, loader); + selectedPositions = new LinkedList<>(); + prefs = Preferences.getInstance(); + helper = new ListHabitsHelper(getContext(), loader); + + adapter.setSelectedPositions(selectedPositions); + adapter.setOnCheckmarkClickListener(this); + adapter.setOnCheckmarkLongClickListener(this); + loader.setListener(this); + loader.setCheckmarkCount(helper.getButtonCount()); + + setAdapter(adapter); + setOnItemClickListener(this); + setOnItemLongClickListener(this); + setDropListener(this); + setDragListener(this); + setFloatViewManager(new HabitsDragSortController()); + setDragEnabled(false); + setLongClickable(true); + } + + public HabitListLoader getLoader() + { + return loader; + } + + public List getSelectedPositions() + { + return selectedPositions; + } + + public void setListener(@Nullable Listener l) + { + this.listener = l; + } + + @Override + public void drop(int from, int to) + { + if(from == to) return; + cancelSelection(); + + loader.reorder(from, to); + adapter.notifyDataSetChanged(); + loader.updateAllHabits(false); + } + + @Override + public void onClick(View v) + { + if (v.getId() != R.id.tvCheck) return; + + if (prefs.isShortToggleEnabled()) toggleCheckmark(v); + else if(listener != null) listener.onInvalidToggle(); + } + + @Override + public boolean onLongClick(View v) + { + lastLongClick = new Date().getTime(); + if (v.getId() != R.id.tvCheck) return true; + if (prefs.isShortToggleEnabled()) return true; + toggleCheckmark(v); + return true; + } + + public void toggleShowArchived() + { + showArchived = !showArchived; + loader.setIncludeArchived(showArchived); + loader.updateAllHabits(true); + } + + private void toggleCheckmark(View v) + { + Long id = helper.getHabitIdFromCheckmarkView(v); + Habit habit = loader.habits.get(id); + if(habit == null) return; + + float x = v.getX() + v.getWidth() / 2.0f + ((View) v.getParent()).getX(); + float y = v.getY() + v.getHeight() / 2.0f + ((View) v.getParent()).getY(); + helper.triggerRipple((View) v.getParent().getParent(), x, y); + + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + helper.toggleCheckmarkView(v, habit); + + long timestamp = helper.getTimestampFromCheckmarkView(v); + + if(listener != null) listener.onToggleCheckmark(habit, timestamp); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + if (new Date().getTime() - lastLongClick < 1000) return; + + if(selectedPositions.isEmpty()) + { + Habit habit = loader.habitsList.get(position); + if(listener != null) listener.onHabitClick(habit); + } + else + { + toggleItemSelected(position); + adapter.notifyDataSetChanged(); + } + } + + private void toggleItemSelected(int position) + { + int k = selectedPositions.indexOf(position); + if(k < 0) selectedPositions.add(position); + else selectedPositions.remove(k); + + if(listener != null) + { + if (selectedPositions.isEmpty()) listener.onHabitSelectionFinish(); + else listener.onHabitSelectionChange(); + } + } + + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) + { + selectHabit(position); + return true; + } + + private void selectHabit(int position) + { + if(!selectedPositions.contains(position)) selectedPositions.add(position); + adapter.notifyDataSetChanged(); + + if(listener != null) + { + if (selectedPositions.size() == 1) listener.onHabitSelectionStart(); + else listener.onHabitSelectionChange(); + } + } + + @Override + public void drag(int from, int to) + { + } + + @Override + public void startDrag(int position) + { + selectHabit(position); + } + + public boolean getShowArchived() + { + return showArchived; + } + + public void cancelSelection() + { + selectedPositions.clear(); + adapter.notifyDataSetChanged(); + setDragEnabled(true); + if(listener != null) listener.onHabitSelectionFinish(); + } + + public void refreshData(Long refreshKey) + { + if (refreshKey == null) loader.updateAllHabits(true); + else loader.updateHabit(refreshKey); + } + + @Override + public void onLoadFinished() + { + adapter.notifyDataSetChanged(); + if(listener != null) listener.onDatasetChanged(); + } + + private class HabitsDragSortController extends DragSortController + { + public HabitsDragSortController() + { + super(HabitListView.this); + setRemoveEnabled(false); + } + + @Override + public View onCreateFloatView(int position) + { + return adapter.getView(position, null, null); + } + + @Override + public void onDestroyFloatView(View floatView) + { + } + } + + public interface Listener + { + void onToggleCheckmark(Habit habit, long timestamp); + + void onHabitClick(Habit habit); + + void onHabitSelectionStart(); + + void onHabitSelectionFinish(); + + void onHabitSelectionChange(); + + void onInvalidToggle(); + + void onDatasetChanged(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java index d6d125bde..25557f553 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java @@ -28,7 +28,6 @@ public class ListHabitsController private Screen screen; - public void setScreen(Screen screen) { this.screen = screen; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java index 43fe40eda..b42c9b7ce 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java @@ -23,25 +23,17 @@ import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.view.ActionMode; -import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.view.View.OnLongClickListener; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; -import com.mobeta.android.dslv.DragSortController; -import com.mobeta.android.dslv.DragSortListView; - -import org.isoron.uhabits.utils.Preferences; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.ToggleRepetitionCommand; @@ -49,40 +41,24 @@ import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.ui.HintManager; import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; -import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.utils.InterfaceUtils.OnSavedListener; import org.isoron.uhabits.utils.ReminderUtils; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; - -public class ListHabitsFragment extends Fragment - implements OnSavedListener, OnItemClickListener, OnLongClickListener, - OnClickListener, ListHabitsLoader.Listener, AdapterView.OnItemLongClickListener, - HabitSelectionCallback.Listener, ListHabitsController.Screen +public class ListHabitsFragment extends Fragment implements OnSavedListener, OnClickListener, + HabitListSelectionCallback.Listener, ListHabitsController.Screen { - long lastLongClick = 0; - private boolean showArchived; - private ActionMode actionMode; - private ListHabitsAdapter adapter; - public ListHabitsLoader loader; private HintManager hintManager; private ListHabitsHelper helper; - private List selectedPositions; - private OnHabitClickListener habitClickListener; + private Listener habitClickListener; private BaseActivity activity; - private DragSortListView listView; + private HabitListView listView; private LinearLayout llButtonsHeader; - public ProgressBar progressBar; + private ProgressBar progressBar; private View llEmpty; - private ListHabitsController controller; - private Preferences prefs; - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) @@ -93,23 +69,16 @@ public class ListHabitsFragment extends Fragment llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader); llEmpty = view.findViewById(R.id.llEmpty); progressBar = (ProgressBar) view.findViewById(R.id.progressBar); - progressBar.setVisibility(View.GONE); - - selectedPositions = new LinkedList<>(); - loader = new ListHabitsLoader(); - helper = new ListHabitsHelper(activity, loader); + listView = (HabitListView) view.findViewById(R.id.listView); + TextView tvStarEmpty = (TextView) view.findViewById(R.id.tvStarEmpty); + helper = new ListHabitsHelper(activity, listView.getLoader()); hintManager = new HintManager(activity, llHint); - loader.setListener(this); - loader.setCheckmarkCount(helper.getButtonCount()); - llHint.setOnClickListener(this); - - TextView tvStarEmpty = (TextView) view.findViewById(R.id.tvStarEmpty); tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(activity)); - - createListView(view); + listView.setListener(new HabitListViewListener()); + setHasOptionsMenu(true); if(savedInstanceState != null) { @@ -118,75 +87,36 @@ public class ListHabitsFragment extends Fragment if(frag != null) frag.setOnSavedListener(this); } - loader.updateAllHabits(true); - - controller = new ListHabitsController(); - controller.setScreen(this); - prefs = Preferences.getInstance(); - - setHasOptionsMenu(true); return view; } - private void createListView(View view) - { - listView = (DragSortListView) view.findViewById(R.id.listView); - adapter = new ListHabitsAdapter(getActivity(), loader); - adapter.setSelectedPositions(selectedPositions); - adapter.setOnCheckmarkClickListener(this); - adapter.setOnCheckmarkLongClickListener(this); - - DragSortListView.DragListener dragListener = new HabitsDragListener(); - DragSortController dragSortController = new HabitsDragSortController(); - - listView.setAdapter(adapter); - listView.setOnItemClickListener(this); - listView.setOnItemLongClickListener(this); - listView.setDropListener(new HabitsDropListener()); - listView.setDragListener(dragListener); - listView.setFloatViewManager(dragSortController); - listView.setDragEnabled(true); - listView.setLongClickable(true); - } - @Override @SuppressWarnings("deprecation") public void onAttach(Activity activity) { super.onAttach(activity); this.activity = (BaseActivity) activity; - habitClickListener = (OnHabitClickListener) activity; + habitClickListener = (Listener) activity; } @Override public void onResume() { super.onResume(); - Long timestamp = loader.getLastLoadTimestamp(); - - if (timestamp != null && timestamp != DateUtils.getStartOfToday()) - loader.updateAllHabits(true); + listView.refreshData(null); helper.updateEmptyMessage(llEmpty); helper.updateHeader(llButtonsHeader); hintManager.showHintIfAppropriate(); - adapter.notifyDataSetChanged(); - } - - @Override - public void onLoadFinished() - { - adapter.notifyDataSetChanged(); - helper.updateEmptyMessage(llEmpty); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.list_habits_options, menu); + inflater.inflate(R.menu.list_habits_fragment, menu); MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived); - showArchivedItem.setChecked(showArchived); + showArchivedItem.setChecked(listView.getShowArchived()); } @Override @@ -209,9 +139,7 @@ public class ListHabitsFragment extends Fragment private void toggleShowArchived() { - showArchived = !showArchived; - loader.setIncludeArchived(showArchived); - loader.updateAllHabits(true); + listView.toggleShowArchived(); activity.invalidateOptionsMenu(); } @@ -222,55 +150,26 @@ public class ListHabitsFragment extends Fragment frag.show(getFragmentManager(), "editHabit"); } - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) + private void startActionMode() { - if (new Date().getTime() - lastLongClick < 1000) return; - - if(actionMode == null) - { - Habit habit = loader.habitsList.get(position); - habitClickListener.onHabitClicked(habit); - } - else - { - toggleItemSelected(position); - adapter.notifyDataSetChanged(); - } + HabitListSelectionCallback callback = + new HabitListSelectionCallback(activity, listView.getLoader()); + callback.setSelectedPositions(listView.getSelectedPositions()); + callback.setOnSavedListener(this); + callback.setListener(this); + actionMode = activity.startSupportActionMode(callback); } - private void toggleItemSelected(int position) + private void finishActionMode() { - int k = selectedPositions.indexOf(position); - if(k < 0) selectedPositions.add(position); - else selectedPositions.remove(k); - - if(selectedPositions.isEmpty()) actionMode.finish(); - else actionMode.invalidate(); + if(actionMode != null) actionMode.finish(); } @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) - { - selectHabit(position); - return true; - } - - private void selectHabit(int position) - { - if(!selectedPositions.contains(position)) selectedPositions.add(position); - adapter.notifyDataSetChanged(); - if(actionMode == null) startSupportActionMode(); - actionMode.invalidate(); - } - - private void startSupportActionMode() + public void onActionModeDestroyed(ActionMode mode) { - HabitSelectionCallback callback = new HabitSelectionCallback(activity, loader); - callback.setSelectedPositions(selectedPositions); - callback.setOnSavedListener(this); - callback.setListener(this); - actionMode = activity.startSupportActionMode(callback); + actionMode = null; + listView.cancelSelection(); } @Override @@ -280,49 +179,12 @@ public class ListHabitsFragment extends Fragment if (h == null) activity.executeCommand(command, null); else activity.executeCommand(command, h.getId()); - adapter.notifyDataSetChanged(); - - ReminderUtils.createReminderAlarms(activity); - - if(actionMode != null) actionMode.finish(); - } - - @Override - public boolean onLongClick(View v) - { - lastLongClick = new Date().getTime(); - - switch (v.getId()) - { - case R.id.tvCheck: - onCheckmarkLongClick(v); - return true; - } - - return false; - } - - private void onCheckmarkLongClick(View v) - { - if (prefs.isShortToggleEnabled()) return; - toggleCheckmark(v); - } - - private void toggleCheckmark(View v) - { - Long id = helper.getHabitIdFromCheckmarkView(v); - Habit habit = loader.habits.get(id); - if(habit == null) return; - float x = v.getX() + v.getWidth() / 2.0f + ((View) v.getParent()).getX(); - float y = v.getY() + v.getHeight() / 2.0f + ((View) v.getParent()).getY(); - helper.triggerRipple((View) v.getParent().getParent(), x, y); + listView.refreshData(null); - listView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - helper.toggleCheckmarkView(v, habit); + ReminderUtils.createReminderAlarms(activity); - long timestamp = helper.getTimestampFromCheckmarkView(v); - executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId()); + finishActionMode(); } private void executeCommand(Command c, Long refreshKey) @@ -333,84 +195,72 @@ public class ListHabitsFragment extends Fragment @Override public void onClick(View v) { - switch (v.getId()) - { - case R.id.tvCheck: - if (prefs.isShortToggleEnabled()) toggleCheckmark(v); - else activity.showMessage(R.string.long_press_to_toggle); - break; - - case R.id.llHint: - hintManager.dismissHint(); - break; - } + if (v.getId() == R.id.llHint) + hintManager.dismissHint(); } public void onPostExecuteCommand(Long refreshKey) { - if (refreshKey == null) loader.updateAllHabits(true); - else loader.updateHabit(refreshKey); + listView.refreshData(refreshKey); } - @Override - public void onActionModeDestroyed(ActionMode mode) + public ProgressBar getProgressBar() { - actionMode = null; - selectedPositions.clear(); - adapter.notifyDataSetChanged(); - listView.setDragEnabled(true); + return progressBar; + } + + public void refresh(Long refreshKey) + { + listView.refreshData(refreshKey); } - public interface OnHabitClickListener + public interface Listener { - void onHabitClicked(Habit habit); + void onHabitClick(Habit habit); } - private class HabitsDragSortController extends DragSortController + private class HabitListViewListener implements HabitListView.Listener { - public HabitsDragSortController() + @Override + public void onToggleCheckmark(Habit habit, long timestamp) { - super(ListHabitsFragment.this.listView); - setRemoveEnabled(false); + executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId()); } @Override - public View onCreateFloatView(int position) + public void onHabitClick(Habit habit) { - return adapter.getView(position, null, null); + habitClickListener.onHabitClick(habit); } @Override - public void onDestroyFloatView(View floatView) + public void onHabitSelectionStart() { + if(actionMode == null) startActionMode(); } - } - private class HabitsDropListener implements DragSortListView.DropListener - { @Override - public void drop(int from, int to) + public void onHabitSelectionFinish() { - if(from == to) return; - if(actionMode != null) actionMode.finish(); + finishActionMode(); + } - loader.reorder(from, to); - adapter.notifyDataSetChanged(); - loader.updateAllHabits(false); + @Override + public void onHabitSelectionChange() + { + if(actionMode != null) actionMode.invalidate(); } - } - private class HabitsDragListener implements DragSortListView.DragListener - { @Override - public void drag(int from, int to) + public void onInvalidToggle() { + activity.showMessage(R.string.long_press_to_toggle); } @Override - public void startDrag(int position) + public void onDatasetChanged() { - selectHabit(position); + helper.updateEmptyMessage(llEmpty); } } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java index 3c74153ef..c8ab3fcac 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java @@ -50,9 +50,9 @@ public class ListHabitsHelper private final int mediumContrastColor; private final Context context; - private final ListHabitsLoader loader; + private final HabitListLoader loader; - public ListHabitsHelper(Context context, ListHabitsLoader loader) + public ListHabitsHelper(Context context, HabitListLoader loader) { this.context = context; this.loader = loader; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 630d47fcb..43d3261b3 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -260,7 +260,7 @@ public class ShowHabitFragment extends Fragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.show_habit_fragment_menu, menu); + inflater.inflate(R.menu.show_habit_fragment, menu); } @Override diff --git a/app/src/main/res/layout/list_habits_fragment.xml b/app/src/main/res/layout/list_habits_fragment.xml index bf97d95be..9fba9281f 100644 --- a/app/src/main/res/layout/list_habits_fragment.xml +++ b/app/src/main/res/layout/list_habits_fragment.xml @@ -24,7 +24,7 @@ android:layout_height="match_parent" android:background="#ffffff"> - + + + + diff --git a/app/src/main/res/menu/list_habits_context.xml b/app/src/main/res/menu/list_habits_selection.xml similarity index 100% rename from app/src/main/res/menu/list_habits_context.xml rename to app/src/main/res/menu/list_habits_selection.xml diff --git a/app/src/main/res/menu/list_habits_menu.xml b/app/src/main/res/menu/main_activity.xml similarity index 82% rename from app/src/main/res/menu/list_habits_menu.xml rename to app/src/main/res/menu/main_activity.xml index d8b8f6bad..c80f5945f 100644 --- a/app/src/main/res/menu/list_habits_menu.xml +++ b/app/src/main/res/menu/main_activity.xml @@ -22,24 +22,12 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> - - - - Date: Fri, 27 May 2016 14:06:55 -0400 Subject: [PATCH 006/184] Fix bug that caused checkmarks not to update sometimes --- app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 da8e4ea77..7c949491b 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -157,8 +157,7 @@ public class CheckmarkList final long day = DateUtils.millisecondsInOneDay; Checkmark newestCheckmark = findNewest(); - if(newestCheckmark != null) - from = Math.max(from, newestCheckmark.timestamp + day); + if(newestCheckmark != null) from = newestCheckmark.timestamp + day; if(from > to) return; From 071cad73d4869945a1cb8ec35247487af3728314 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 27 May 2016 22:26:33 -0400 Subject: [PATCH 007/184] Make models observable, refactor ShowHabitFragment --- .../uhabits/HabitBroadcastReceiver.java | 9 +- .../java/org/isoron/uhabits/MainActivity.java | 35 +- .../uhabits/commands/CommandRunner.java | 75 ++++ .../isoron/uhabits/models/CheckmarkList.java | 3 + .../java/org/isoron/uhabits/models/Habit.java | 9 + .../isoron/uhabits/models/RepetitionList.java | 2 + .../org/isoron/uhabits/models/ScoreList.java | 3 + .../org/isoron/uhabits/models/StreakList.java | 3 + .../uhabits/tasks/ToggleRepetitionTask.java | 5 +- .../org/isoron/uhabits/ui/BaseActivity.java | 109 +++--- .../habits/edit/EditHabitDialogFragment.java | 23 +- .../ui/habits/list/HabitListLoader.java | 21 +- .../list/HabitListSelectionCallback.java | 113 +++--- .../ui/habits/list/ListHabitsFragment.java | 45 +-- .../ui/habits/show/ShowHabitActivity.java | 4 +- .../ui/habits/show/ShowHabitFragment.java | 321 ++++++------------ .../ui/habits/show/ShowHabitHelper.java | 139 ++++++++ .../isoron/uhabits/utils/InterfaceUtils.java | 6 - .../uhabits/views/HabitFrequencyView.java | 42 ++- .../uhabits/views/HabitHistoryView.java | 40 ++- .../isoron/uhabits/views/HabitScoreView.java | 40 ++- .../isoron/uhabits/views/HabitStreakView.java | 36 +- 22 files changed, 638 insertions(+), 445 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java index 455d41a15..c923b4e34 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java @@ -36,6 +36,8 @@ import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import android.support.v4.content.LocalBroadcastManager; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.models.Checkmark; @@ -115,9 +117,12 @@ public class HabitBroadcastReceiver extends BroadcastReceiver long habitId = ContentUris.parseId(data); Habit habit = Habit.get(habitId); if(habit != null) - habit.repetitions.toggle(timestamp); - dismissNotification(context, habitId); + { + ToggleRepetitionCommand command = new ToggleRepetitionCommand(habit, timestamp); + CommandRunner.getInstance().execute(command, habitId); + } + dismissNotification(context, habitId); sendRefreshBroadcast(context); } diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 8d425572a..1655c4de2 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -19,7 +19,6 @@ package org.isoron.uhabits; -import android.content.Context; import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.net.Uri; @@ -31,21 +30,18 @@ import android.support.v7.app.ActionBar; import android.view.Menu; import android.view.MenuItem; -import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.tasks.ProgressBar; -import org.isoron.uhabits.ui.about.AboutActivity; import org.isoron.uhabits.ui.AndroidProgressBar; import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.ui.intro.IntroActivity; +import org.isoron.uhabits.ui.about.AboutActivity; import org.isoron.uhabits.ui.habits.list.ListHabitsFragment; +import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; +import org.isoron.uhabits.ui.intro.IntroActivity; import org.isoron.uhabits.ui.settings.FilePickerDialog; import org.isoron.uhabits.ui.settings.SettingsActivity; -import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.widgets.WidgetManager; import java.io.File; @@ -196,31 +192,6 @@ public class MainActivity extends BaseActivity showHabitScreen(habit); } - @Override - public void onPostExecuteCommand(Long refreshKey) - { - listHabitsFragment.onPostExecuteCommand(refreshKey); - - new BaseTask() - { - @Override - protected void doInBackground() - { - dismissNotifications(MainActivity.this); - WidgetManager.updateWidgets(MainActivity.this); - } - }.execute(); - } - - private void dismissNotifications(Context context) - { - for(Habit h : Habit.getHabitsWithReminder()) - { - if(h.checkmarks.getTodayValue() != Checkmark.UNCHECKED) - HabitBroadcastReceiver.dismissNotification(context, h); - } - } - private void showHabitScreen(Habit habit) { Intent intent = new Intent(this, ShowHabitActivity.class); diff --git a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java new file mode 100644 index 000000000..05d541dbc --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java @@ -0,0 +1,75 @@ +/* + * 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.commands; + +import org.isoron.uhabits.tasks.BaseTask; + +import java.util.LinkedList; + +public class CommandRunner +{ + public interface Listener + { + void onCommandExecuted(Command command, Long refreshKey); + } + + private static CommandRunner singleton; + private LinkedList listeners; + + private CommandRunner() + { + listeners = new LinkedList<>(); + } + + public static CommandRunner getInstance() + { + if(singleton == null) singleton = new CommandRunner(); + return singleton; + } + + public void execute(final Command command, final Long refreshKey) + { + new BaseTask() + { + @Override + protected void doInBackground() + { + command.execute(); + } + + @Override + protected void onPostExecute(Void aVoid) + { + for(Listener l : listeners) + l.onCommandExecuted(command, refreshKey); + } + }.execute(); + } + + public void addListener(Listener l) + { + listeners.add(l); + } + + public void removeListener(Listener l) + { + listeners.remove(l); + } +} 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 7c949491b..c0306cdb3 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -41,6 +41,7 @@ import java.util.List; public class CheckmarkList { private Habit habit; + public ModelObservable observable = new ModelObservable(); public CheckmarkList(Habit habit) { @@ -59,6 +60,8 @@ public class CheckmarkList .where("habit = ?", habit.getId()) .and("timestamp >= ?", timestamp) .execute(); + + observable.notifyListeners(); } /** diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 3751996cd..8752fbb1f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -147,6 +147,8 @@ public class Habit extends Model @NonNull public CheckmarkList checkmarks; + public ModelObservable observable = new ModelObservable(); + /** * Constructs a habit with the same attributes as the specified habit. * @@ -348,6 +350,8 @@ public class Habit extends Model this.reminderDays = model.reminderDays; this.highlight = model.highlight; this.archived = model.archived; + + observable.notifyListeners(); } /** @@ -424,6 +428,8 @@ public class Habit extends Model finally { ActiveAndroid.endTransaction(); + for(Habit h : habits) + h.observable.notifyListeners(); } } @@ -456,6 +462,8 @@ public class Habit extends Model public static void setColor(@NonNull List habits, int color) { updateAttributes(habits, color, null); + for(Habit h : habits) + h.observable.notifyListeners(); } /** @@ -476,6 +484,7 @@ public class Habit extends Model reminderHour = null; reminderMin = null; reminderDays = DateUtils.ALL_WEEK_DAYS; + observable.notifyListeners(); } /** diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index 1e4227e9f..f9766645d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -41,6 +41,7 @@ public class RepetitionList { @NonNull private Habit habit; + public ModelObservable observable = new ModelObservable(); public RepetitionList(@NonNull Habit habit) { @@ -105,6 +106,7 @@ public class RepetitionList habit.scores.invalidateNewerThan(timestamp); habit.checkmarks.deleteNewerThan(timestamp); habit.streaks.deleteNewerThan(timestamp); + observable.notifyListeners(); } private void insert(long timestamp) diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index b207b130c..e49ec56ca 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -44,6 +44,7 @@ public class ScoreList { @NonNull private Habit habit; + public ModelObservable observable = new ModelObservable(); /** * Constructs a new ScoreList associated with the given habit. @@ -75,6 +76,8 @@ public class ScoreList .where("habit = ?", habit.getId()) .and("timestamp >= ?", timestamp) .execute(); + + observable.notifyListeners(); } /** diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java index a47409ef1..523b9e339 100644 --- a/app/src/main/java/org/isoron/uhabits/models/StreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java @@ -37,6 +37,7 @@ import java.util.List; public class StreakList { private Habit habit; + public ModelObservable observable = new ModelObservable(); public StreakList(Habit habit) { @@ -156,5 +157,7 @@ public class StreakList .where("habit = ?", habit.getId()) .and("end >= ?", timestamp - DateUtils.millisecondsInOneDay) .execute(); + + observable.notifyListeners(); } } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java index 3b46ec95c..828656eb3 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java @@ -19,6 +19,8 @@ package org.isoron.uhabits.tasks; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.models.Habit; public class ToggleRepetitionTask extends BaseTask @@ -40,7 +42,8 @@ public class ToggleRepetitionTask extends BaseTask @Override protected void doInBackground() { - habit.repetitions.toggle(timestamp); + ToggleRepetitionCommand command = new ToggleRepetitionCommand(habit, timestamp); + CommandRunner.getInstance().execute(command, habit.getId()); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java index d8d6f26e9..e7ed2471b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java @@ -20,9 +20,9 @@ package org.isoron.uhabits.ui; import android.app.backup.BackupManager; +import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v7.app.ActionBar; @@ -31,20 +31,21 @@ import android.support.v7.widget.Toolbar; import android.view.View; import android.widget.Toast; +import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.widgets.WidgetManager; -import java.util.LinkedList; - -abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler +abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler, + CommandRunner.Listener { - private static int MAX_UNDO_LEVEL = 15; - - private LinkedList undoList; - private LinkedList redoList; private Toast toast; Thread.UncaughtExceptionHandler androidExceptionHandler; @@ -58,39 +59,20 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this); - - undoList = new LinkedList<>(); - redoList = new LinkedList<>(); } - public void executeCommand(Command command, Long refreshKey) + @Override + protected void onResume() { - executeCommand(command, false, refreshKey); + super.onResume(); + CommandRunner.getInstance().addListener(this); } - protected void undo() - { - if (undoList.isEmpty()) - { - showMessage(R.string.toast_nothing_to_undo); - return; - } - - Command last = undoList.pop(); - redoList.push(last); - last.undo(); - showMessage(last.getUndoStringId()); - } - - protected void redo() + @Override + protected void onPause() { - if (redoList.isEmpty()) - { - showMessage(R.string.toast_nothing_to_redo); - return; - } - Command last = redoList.pop(); - executeCommand(last, false, null); + CommandRunner.getInstance().removeListener(this); + super.onPause(); } public void showMessage(Integer stringId) @@ -101,34 +83,6 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U toast.show(); } - public void executeCommand(final Command command, Boolean clearRedoStack, final Long refreshKey) - { - undoList.push(command); - - if (undoList.size() > MAX_UNDO_LEVEL) undoList.removeLast(); - if (clearRedoStack) redoList.clear(); - - new AsyncTask() - { - @Override - protected Void doInBackground(Void... params) - { - command.execute(); - return null; - } - - @Override - protected void onPostExecute(Void aVoid) - { - BaseActivity.this.onPostExecuteCommand(refreshKey); - BackupManager.dataChanged("org.isoron.uhabits"); - } - }.execute(); - - - showMessage(command.getExecuteStringId()); - } - protected void setupSupportActionBar(boolean homeButtonEnabled) { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); @@ -146,10 +100,6 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U actionBar.setDisplayHomeAsUpEnabled(true); } - public void onPostExecuteCommand(Long refreshKey) - { - } - @Override public void uncaughtException(Thread thread, Throwable ex) { @@ -203,4 +153,29 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U view = findViewById(R.id.headerShadow); if(view != null) view.setVisibility(View.GONE); } + + private void dismissNotifications(Context context) + { + for(Habit h : Habit.getHabitsWithReminder()) + { + if(h.checkmarks.getTodayValue() != Checkmark.UNCHECKED) + HabitBroadcastReceiver.dismissNotification(context, h); + } + } + + @Override + public void onCommandExecuted(Command command, Long refreshKey) + { + showMessage(command.getExecuteStringId()); + new BaseTask() + { + @Override + protected void doInBackground() + { + dismissNotifications(BaseActivity.this); + BackupManager.dataChanged("org.isoron.uhabits"); + WidgetManager.updateWidgets(BaseActivity.this); + } + }.execute(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java index b70b2ce2b..9a84b84b3 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java @@ -42,12 +42,12 @@ import com.android.datetimepicker.time.TimePickerDialog; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; +import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.commands.EditHabitCommand; -import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.utils.DateUtils; import java.util.Arrays; @@ -59,8 +59,6 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment static final int EDIT_MODE = 0; static final int CREATE_MODE = 1; - private InterfaceUtils.OnSavedListener onSavedListener; - private Habit originalHabit; private Habit modifiedHabit; @@ -202,11 +200,6 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment } } - public void setOnSavedListener(InterfaceUtils.OnSavedListener onSavedListener) - { - this.onSavedListener = onSavedListener; - } - @Override public void onClick(View v) { @@ -264,21 +257,17 @@ public class EditHabitDialogFragment extends AppCompatDialogFragment if (!validate()) return; - Command command = null; - Habit savedHabit = null; - if (mode == EDIT_MODE) { - command = new EditHabitCommand(originalHabit, modifiedHabit); - savedHabit = originalHabit; + Command command = new EditHabitCommand(originalHabit, modifiedHabit); + CommandRunner.getInstance().execute(command, originalHabit.getId()); } else if (mode == CREATE_MODE) { - command = new CreateHabitCommand(modifiedHabit); + Command command = new CreateHabitCommand(modifiedHabit); + CommandRunner.getInstance().execute(command, null); } - if (onSavedListener != null) onSavedListener.onSaved(command, savedHabit); - dismiss(); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java index 496d336d4..d80b178fa 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java @@ -21,6 +21,8 @@ package org.isoron.uhabits.ui.habits.list; import android.support.annotation.Nullable; +import org.isoron.uhabits.commands.Command; +import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; @@ -28,7 +30,7 @@ import org.isoron.uhabits.tasks.BaseTask; import java.util.HashMap; import java.util.List; -public class HabitListLoader +public class HabitListLoader implements CommandRunner.Listener { public interface Listener { @@ -201,4 +203,21 @@ public class HabitListLoader } }.execute(); } + + public void onResume() + { + CommandRunner.getInstance().addListener(this); + } + + public void onPause() + { + CommandRunner.getInstance().removeListener(this); + } + + @Override + public void onCommandExecuted(Command command, Long refreshKey) + { + if(refreshKey == null) updateAllHabits(true); + else updateHabit(refreshKey); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java index 41378e8d0..1091841e6 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java @@ -31,13 +31,13 @@ import com.android.colorpicker.ColorPickerSwatch; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.ArchiveHabitsCommand; import org.isoron.uhabits.commands.ChangeHabitColorCommand; +import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.InterfaceUtils; import java.util.LinkedList; import java.util.List; @@ -48,7 +48,6 @@ public class HabitListSelectionCallback implements ActionMode.Callback private List selectedPositions; private BaseActivity activity; private Listener listener; - private InterfaceUtils.OnSavedListener onSavedListener; public interface Listener { @@ -67,11 +66,6 @@ public class HabitListSelectionCallback implements ActionMode.Callback this.listener = listener; } - public void setOnSavedListener(InterfaceUtils.OnSavedListener onSavedListener) - { - this.onSavedListener = onSavedListener; - } - public void setSelectedPositions(List selectedPositions) { this.selectedPositions = selectedPositions; @@ -134,64 +128,33 @@ public class HabitListSelectionCallback implements ActionMode.Callback switch (item.getItemId()) { case R.id.action_archive_habit: - activity.executeCommand(new ArchiveHabitsCommand(selectedHabits), null); + archiveHabits(selectedHabits); mode.finish(); return true; case R.id.action_unarchive_habit: - activity.executeCommand(new UnarchiveHabitsCommand(selectedHabits), null); + unarchiveHabits(selectedHabits); mode.finish(); return true; case R.id.action_edit_habit: { - EditHabitDialogFragment - frag = EditHabitDialogFragment.editSingleHabitFragment(firstHabit.getId()); - frag.setOnSavedListener(onSavedListener); - frag.show(activity.getSupportFragmentManager(), "editHabit"); + editHabit(firstHabit); + mode.finish(); return true; } case R.id.action_color: { - int originalAndroidColor = ColorUtils.getColor(activity, firstHabit.color); - - ColorPickerDialog picker = ColorPickerDialog.newInstance( - R.string.color_picker_default_title, ColorUtils.getPalette(activity), - originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL); - - picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() - { - public void onColorSelected(int androidColor) - { - int paletteColor = ColorUtils.colorToPaletteIndex(activity, - androidColor); - activity.executeCommand(new ChangeHabitColorCommand(selectedHabits, - paletteColor), null); - mode.finish(); - } - }); - picker.show(activity.getSupportFragmentManager(), "picker"); + showColorPicker(mode, selectedHabits, firstHabit); + mode.finish(); return true; } case R.id.action_delete: { - new AlertDialog.Builder(activity).setTitle(R.string.delete_habits) - .setMessage(R.string.delete_habits_message) - .setPositiveButton(android.R.string.yes, - new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - activity.executeCommand( - new DeleteHabitsCommand(selectedHabits), null); - mode.finish(); - } - }).setNegativeButton(android.R.string.no, null) - .show(); - + deleteHabits(mode, selectedHabits); + mode.finish(); return true; } } @@ -199,6 +162,64 @@ public class HabitListSelectionCallback implements ActionMode.Callback return false; } + private void deleteHabits(final ActionMode mode, final LinkedList selectedHabits) + { + new AlertDialog.Builder(activity).setTitle(R.string.delete_habits) + .setMessage(R.string.delete_habits_message) + .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + CommandRunner.getInstance() + .execute(new DeleteHabitsCommand(selectedHabits), null); + mode.finish(); + } + }) + .setNegativeButton(android.R.string.no, null) + .show(); + } + + private void showColorPicker(final ActionMode mode, final LinkedList selectedHabits, + Habit firstHabit) + { + int originalAndroidColor = ColorUtils.getColor(activity, firstHabit.color); + + ColorPickerDialog picker = + ColorPickerDialog.newInstance(R.string.color_picker_default_title, + ColorUtils.getPalette(activity), originalAndroidColor, 4, + ColorPickerDialog.SIZE_SMALL); + + picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() + { + public void onColorSelected(int androidColor) + { + int paletteColor = ColorUtils.colorToPaletteIndex(activity, androidColor); + CommandRunner.getInstance() + .execute(new ChangeHabitColorCommand(selectedHabits, paletteColor), null); + mode.finish(); + } + }); + picker.show(activity.getSupportFragmentManager(), "picker"); + } + + private void editHabit(Habit habit) + { + EditHabitDialogFragment + frag = EditHabitDialogFragment.editSingleHabitFragment(habit.getId()); + frag.show(activity.getSupportFragmentManager(), "editHabit"); + } + + private void unarchiveHabits(LinkedList selectedHabits) + { + CommandRunner.getInstance().execute(new UnarchiveHabitsCommand(selectedHabits), null); + } + + private void archiveHabits(LinkedList selectedHabits) + { + CommandRunner.getInstance().execute(new ArchiveHabitsCommand(selectedHabits), null); + } + @Override public void onDestroyActionMode(ActionMode mode) { diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java index b42c9b7ce..cefecc0c9 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java @@ -35,17 +35,15 @@ import android.widget.ProgressBar; import android.widget.TextView; import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.Command; +import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.ui.HintManager; import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.utils.InterfaceUtils.OnSavedListener; -import org.isoron.uhabits.utils.ReminderUtils; -public class ListHabitsFragment extends Fragment implements OnSavedListener, OnClickListener, +public class ListHabitsFragment extends Fragment implements OnClickListener, HabitListSelectionCallback.Listener, ListHabitsController.Screen { private ActionMode actionMode; @@ -84,7 +82,6 @@ public class ListHabitsFragment extends Fragment implements OnSavedListener, OnC { EditHabitDialogFragment frag = (EditHabitDialogFragment) getFragmentManager() .findFragmentByTag("editHabit"); - if(frag != null) frag.setOnSavedListener(this); } return view; @@ -104,12 +101,20 @@ public class ListHabitsFragment extends Fragment implements OnSavedListener, OnC { super.onResume(); + listView.getLoader().onResume(); listView.refreshData(null); helper.updateEmptyMessage(llEmpty); helper.updateHeader(llButtonsHeader); hintManager.showHintIfAppropriate(); } + @Override + public void onPause() + { + listView.getLoader().onPause(); + super.onPause(); + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @@ -146,7 +151,6 @@ public class ListHabitsFragment extends Fragment implements OnSavedListener, OnC private void showCreateHabitScreen() { EditHabitDialogFragment frag = EditHabitDialogFragment.createHabitFragment(); - frag.setOnSavedListener(this); frag.show(getFragmentManager(), "editHabit"); } @@ -155,7 +159,6 @@ public class ListHabitsFragment extends Fragment implements OnSavedListener, OnC HabitListSelectionCallback callback = new HabitListSelectionCallback(activity, listView.getLoader()); callback.setSelectedPositions(listView.getSelectedPositions()); - callback.setOnSavedListener(this); callback.setListener(this); actionMode = activity.startSupportActionMode(callback); } @@ -172,26 +175,6 @@ public class ListHabitsFragment extends Fragment implements OnSavedListener, OnC listView.cancelSelection(); } - @Override - public void onSaved(Command command, Object savedObject) - { - Habit h = (Habit) savedObject; - - if (h == null) activity.executeCommand(command, null); - else activity.executeCommand(command, h.getId()); - - listView.refreshData(null); - - ReminderUtils.createReminderAlarms(activity); - - finishActionMode(); - } - - private void executeCommand(Command c, Long refreshKey) - { - activity.executeCommand(c, refreshKey); - } - @Override public void onClick(View v) { @@ -199,11 +182,6 @@ public class ListHabitsFragment extends Fragment implements OnSavedListener, OnC hintManager.dismissHint(); } - public void onPostExecuteCommand(Long refreshKey) - { - listView.refreshData(refreshKey); - } - public ProgressBar getProgressBar() { return progressBar; @@ -224,7 +202,8 @@ public class ListHabitsFragment extends Fragment implements OnSavedListener, OnC @Override public void onToggleCheckmark(Habit habit, long timestamp) { - executeCommand(new ToggleRepetitionCommand(habit, timestamp), habit.getId()); + CommandRunner.getInstance().execute(new ToggleRepetitionCommand(habit, timestamp), + habit.getId()); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java index a34a61fe4..1d15e642e 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java @@ -42,12 +42,11 @@ public class ShowHabitActivity extends BaseActivity habit = Habit.get(ContentUris.parseId(data)); setContentView(R.layout.show_habit_activity); - setupSupportActionBar(true); setupHabitActionBar(); } - private void setupHabitActionBar() + public void setupHabitActionBar() { if(habit == null) return; @@ -55,7 +54,6 @@ public class ShowHabitActivity extends BaseActivity if(actionBar == null) return; actionBar.setTitle(habit.name); - setupActionBarColor(ColorUtils.getColor(this, habit.color)); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 43d3261b3..3ce2bf349 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -31,60 +31,42 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.Button; import android.widget.Spinner; -import android.widget.TextView; -import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.Command; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.ui.habits.edit.HistoryEditorDialog; -import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.views.HabitDataView; import org.isoron.uhabits.views.HabitFrequencyView; import org.isoron.uhabits.views.HabitHistoryView; import org.isoron.uhabits.views.HabitScoreView; import org.isoron.uhabits.views.HabitStreakView; -import org.isoron.uhabits.views.RingView; import java.util.LinkedList; import java.util.List; public class ShowHabitFragment extends Fragment - implements InterfaceUtils.OnSavedListener, HistoryEditorDialog.Listener, - Spinner.OnItemSelectedListener + implements Spinner.OnItemSelectedListener, ModelObservable.Listener { - @Nullable protected ShowHabitActivity activity; - - @Nullable - private Habit habit; - @Nullable private List dataViews; - @Nullable - private HabitScoreView scoreView; - private int previousScoreInterval; - private float todayScore; - private float lastMonthScore; - private float lastYearScore; + Habit habit; - private int activeColor; - private int inactiveColor; + float todayScore; + float lastMonthScore; + float lastYearScore; + int activeColor; + int inactiveColor; - @Override - public void onStart() - { - super.onStart(); - } + private ShowHabitHelper helper; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -92,37 +74,32 @@ public class ShowHabitFragment extends Fragment { View view = inflater.inflate(R.layout.show_habit, container, false); activity = (ShowHabitActivity) getActivity(); + helper = new ShowHabitHelper(this); habit = activity.getHabit(); - activeColor = ColorUtils.getColor(getContext(), habit.color); - inactiveColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - - updateHeader(view); - - dataViews = new LinkedList<>(); - - Button btEditHistory = (Button) view.findViewById(R.id.btEditHistory); - Spinner sStrengthInterval = (Spinner) view.findViewById(R.id.sStrengthInterval); - - scoreView = (HabitScoreView) view.findViewById(R.id.scoreView); + helper.updateColors(); + helper.updateMainHeader(view); int defaultScoreInterval = InterfaceUtils.getDefaultScoreInterval(getContext()); previousScoreInterval = defaultScoreInterval; setScoreBucketSize(defaultScoreInterval); + Spinner sStrengthInterval = (Spinner) view.findViewById(R.id.sStrengthInterval); sStrengthInterval.setSelection(defaultScoreInterval); sStrengthInterval.setOnItemSelectedListener(this); - dataViews.add((HabitScoreView) view.findViewById(R.id.scoreView)); - dataViews.add((HabitHistoryView) view.findViewById(R.id.historyView)); - dataViews.add((HabitFrequencyView) view.findViewById(R.id.punchcardView)); - dataViews.add((HabitStreakView) view.findViewById(R.id.streakView)); + createDataViews(view); + helper.updateCardHeaders(view); - updateHeaders(view); + bindButtontEditHistory(view); + setHasOptionsMenu(true); - for(HabitDataView dataView : dataViews) - dataView.setHabit(habit); + return view; + } + private void bindButtontEditHistory(View view) + { + Button btEditHistory = (Button) view.findViewById(R.id.btEditHistory); btEditHistory.setOnClickListener(new View.OnClickListener() { @Override @@ -130,75 +107,21 @@ public class ShowHabitFragment extends Fragment { HistoryEditorDialog frag = new HistoryEditorDialog(); frag.setHabit(habit); - frag.setListener(ShowHabitFragment.this); frag.show(getFragmentManager(), "historyEditor"); } }); - - if(savedInstanceState != null) - { - EditHabitDialogFragment fragEdit = (EditHabitDialogFragment) getFragmentManager() - .findFragmentByTag("editHabit"); - HistoryEditorDialog fragEditor = (HistoryEditorDialog) getFragmentManager() - .findFragmentByTag("historyEditor"); - - if(fragEdit != null) fragEdit.setOnSavedListener(this); - if(fragEditor != null) fragEditor.setListener(this); - } - - setHasOptionsMenu(true); - - return view; } - private void updateHeader(View view) + private void createDataViews(View view) { - if(habit == null) return; - - TextView questionLabel = (TextView) view.findViewById(R.id.questionLabel); - questionLabel.setTextColor(activeColor); - questionLabel.setText(habit.description); - - TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel); - if(habit.hasReminder()) - reminderLabel.setText(DateUtils.formatTime(getActivity(), habit.reminderHour, - habit.reminderMin)); - else - reminderLabel.setText(getResources().getString(R.string.reminder_off)); - - TextView frequencyLabel = (TextView) view.findViewById(R.id.frequencyLabel); - frequencyLabel.setText(getFreqText()); - - if(habit.description.isEmpty()) - questionLabel.setVisibility(View.GONE); - } - - private String getFreqText() - { - if(habit == null) - return ""; - - if(habit.freqNum.equals(habit.freqDen)) - return getResources().getString(R.string.every_day); - - if(habit.freqNum == 1) - { - if (habit.freqDen == 7) - return getResources().getString(R.string.every_week); - - if (habit.freqDen % 7 == 0) - return getResources().getString(R.string.every_x_weeks, habit.freqDen / 7); - - return getResources().getString(R.string.every_x_days, habit.freqDen); - } - - String times_every = getResources().getString(R.string.times_every); - - if(habit.freqNum == 1) - times_every = getResources().getString(R.string.time_every); + dataViews = new LinkedList<>(); + dataViews.add((HabitScoreView) view.findViewById(R.id.scoreView)); + dataViews.add((HabitHistoryView) view.findViewById(R.id.historyView)); + dataViews.add((HabitFrequencyView) view.findViewById(R.id.punchcardView)); + dataViews.add((HabitStreakView) view.findViewById(R.id.streakView)); - return String.format("%d %s %d %s", habit.freqNum, times_every, habit.freqDen, - getResources().getString(R.string.days)); + for(HabitDataView dataView : dataViews) + dataView.setHabit(habit); } @Override @@ -208,166 +131,114 @@ public class ShowHabitFragment extends Fragment refreshData(); } - private void updateScore(View view) + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if(habit == null) return; - if(view == null) return; - - float todayPercentage = todayScore / Score.MAX_VALUE; - float monthDiff = todayPercentage - (lastMonthScore / Score.MAX_VALUE); - float yearDiff = todayPercentage - (lastYearScore / Score.MAX_VALUE); - - RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); - int androidColor = ColorUtils.getColor(getActivity(), habit.color); - scoreRing.setColor(androidColor); - scoreRing.setPercentage(todayPercentage); - - TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel); - TextView monthDiffLabel = (TextView) view.findViewById(R.id.monthDiffLabel); - TextView yearDiffLabel = (TextView) view.findViewById(R.id.yearDiffLabel); - - scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); - - String minus = "\u2212"; - monthDiffLabel.setText(String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), - Math.abs(monthDiff) * 100)); - yearDiffLabel.setText( - String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), Math.abs(yearDiff) * 100)); - - monthDiffLabel.setTextColor(monthDiff >= 0 ? activeColor : inactiveColor); - yearDiffLabel.setTextColor(yearDiff >= 0 ? activeColor : inactiveColor); + inflater.inflate(R.menu.show_habit_fragment, menu); } - private void updateHeaders(View view) + @Override + public boolean onOptionsItemSelected(MenuItem item) { - updateColor(view, R.id.tvHistory); - updateColor(view, R.id.tvOverview); - updateColor(view, R.id.tvStrength); - updateColor(view, R.id.tvStreaks); - updateColor(view, R.id.tvWeekdayFreq); - updateColor(view, R.id.scoreLabel); + if (item.getItemId() == R.id.action_edit_habit) + return showEditHabitDialog(); + + return false; } - private void updateColor(View view, int viewId) + private boolean showEditHabitDialog() { - if(habit == null || activity == null) return; + if(habit == null) return false; - TextView textView = (TextView) view.findViewById(viewId); - int androidColor = ColorUtils.getColor(activity, habit.color); - textView.setTextColor(androidColor); + EditHabitDialogFragment frag = EditHabitDialogFragment.editSingleHabitFragment(habit.getId()); + frag.show(getFragmentManager(), "editHabit"); + return true; } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + public void refreshData() { - inflater.inflate(R.menu.show_habit_fragment, menu); + new RefreshTask().execute(); } @Override - public boolean onOptionsItemSelected(MenuItem item) + public void onItemSelected(AdapterView parent, View view, int position, long id) { - if(habit == null) return false; - - switch (item.getItemId()) - { - case R.id.action_edit_habit: - { - EditHabitDialogFragment - frag = EditHabitDialogFragment.editSingleHabitFragment(habit.getId()); - frag.setOnSavedListener(this); - frag.show(getFragmentManager(), "editHabit"); - return true; - } - } - - return false; + if(parent.getId() == R.id.sStrengthInterval) + setScoreBucketSize(position); } - @Override - public void onSaved(Command command, Object savedObject) + private void setScoreBucketSize(int position) { - if(activity == null) return; - Habit h = (Habit) savedObject; + if(getView() == null) return; - if (h == null) activity.executeCommand(command, null); - else activity.executeCommand(command, h.getId()); + HabitScoreView scoreView = (HabitScoreView) getView().findViewById(R.id.scoreView); + scoreView.setBucketSize(HabitScoreView.DEFAULT_BUCKET_SIZES[position]); - ReminderUtils.createReminderAlarms(activity); - HabitBroadcastReceiver.sendRefreshBroadcast(getActivity()); + if(position != previousScoreInterval) + refreshData(); - activity.recreate(); + InterfaceUtils.setDefaultScoreInterval(getContext(), position); + previousScoreInterval = position; } @Override - public void onHistoryEditorClosed() + public void onNothingSelected(AdapterView parent) { - refreshData(); - HabitBroadcastReceiver.sendRefreshBroadcast(getActivity()); } - public void refreshData() + @Override + public void onModelChange() { - new BaseTask() + refreshData(); + activity.runOnUiThread(new Runnable() { @Override - protected void doInBackground() + public void run() { - if(habit == null) return; - if(dataViews == null) return; - - long today = DateUtils.getStartOfToday(); - long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; - long lastYear = today - 365 * DateUtils.millisecondsInOneDay; - - todayScore = (float) habit.scores.getTodayValue(); - lastMonthScore = (float) habit.scores.getValue(lastMonth); - lastYearScore = (float) habit.scores.getValue(lastYear); - - int count = 0; - for(HabitDataView view : dataViews) - { - view.refreshData(); - publishProgress(count++); - } + helper.updateColors(); + helper.updateMainHeader(getView()); + helper.updateCardHeaders(getView()); + if(activity != null) activity.setupHabitActionBar(); } - - @Override - protected void onProgressUpdate(Integer... values) - { - updateScore(getView()); - if(dataViews == null) return; - dataViews.get(values[0]).postInvalidate(); - } - }.execute(); - + }); } - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) + private class RefreshTask extends BaseTask { - if(parent.getId() == R.id.sStrengthInterval) - setScoreBucketSize(position); - } + @Override + protected void doInBackground() + { + if(habit == null) return; + if(dataViews == null) return; - private void setScoreBucketSize(int position) - { - if(scoreView == null) return; + long today = DateUtils.getStartOfToday(); + long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; + long lastYear = today - 365 * DateUtils.millisecondsInOneDay; - scoreView.setBucketSize(HabitScoreView.DEFAULT_BUCKET_SIZES[position]); + todayScore = (float) habit.scores.getTodayValue(); + lastMonthScore = (float) habit.scores.getValue(lastMonth); + lastYearScore = (float) habit.scores.getValue(lastYear); + } - if(position != previousScoreInterval) + @Override + protected void onPostExecute(Void aVoid) { - refreshData(); - HabitBroadcastReceiver.sendRefreshBroadcast(getActivity()); + helper.updateScore(getView()); + super.onPostExecute(aVoid); } - - InterfaceUtils.setDefaultScoreInterval(getContext(), position); - previousScoreInterval = position; } @Override - public void onNothingSelected(AdapterView parent) + public void onStart() { + super.onStart(); + habit.observable.addListener(this); + } + @Override + public void onPause() + { + habit.observable.removeListener(this); + super.onPause(); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java new file mode 100644 index 000000000..026327d22 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -0,0 +1,139 @@ +/* + * 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.ui.habits.show; + +import android.content.res.Resources; +import android.view.View; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.views.RingView; + +public class ShowHabitHelper +{ + private ShowHabitFragment fragment; + + public ShowHabitHelper(ShowHabitFragment fragment) + { + this.fragment = fragment; + } + + String getFreqText() + { + if(fragment.habit == null) return ""; + + Resources resources = fragment.getResources(); + Integer freqNum = fragment.habit.freqNum; + Integer freqDen = fragment.habit.freqDen; + + if (freqNum.equals(freqDen)) return resources.getString(R.string.every_day); + + if (freqNum == 1) + { + if (freqDen == 7) return resources.getString(R.string.every_week); + if (freqDen % 7 == 0) return resources.getString(R.string.every_x_weeks, freqDen / 7); + return resources.getString(R.string.every_x_days, freqDen); + } + + String times_every = resources.getString(R.string.time_every); + return String.format("%d %s %d %s", freqNum, times_every, freqDen, + resources.getString(R.string.days)); + } + + void updateScore(View view) + { + if (fragment.habit == null) return; + if (view == null) return; + + float todayPercentage = fragment.todayScore / Score.MAX_VALUE; + float monthDiff = todayPercentage - (fragment.lastMonthScore / Score.MAX_VALUE); + float yearDiff = todayPercentage - (fragment.lastYearScore / Score.MAX_VALUE); + + RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); + int androidColor = ColorUtils.getColor(fragment.getActivity(), fragment.habit.color); + scoreRing.setColor(androidColor); + scoreRing.setPercentage(todayPercentage); + + TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel); + TextView monthDiffLabel = (TextView) view.findViewById(R.id.monthDiffLabel); + TextView yearDiffLabel = (TextView) view.findViewById(R.id.yearDiffLabel); + + scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); + + String minus = "\u2212"; + monthDiffLabel.setText(String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), + Math.abs(monthDiff) * 100)); + yearDiffLabel.setText( + String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), Math.abs(yearDiff) * 100)); + + monthDiffLabel.setTextColor(monthDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); + yearDiffLabel.setTextColor(yearDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); + } + + void updateMainHeader(View view) + { + if (fragment.habit == null) return; + + TextView questionLabel = (TextView) view.findViewById(R.id.questionLabel); + questionLabel.setTextColor(fragment.activeColor); + questionLabel.setText(fragment.habit.description); + + TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel); + if (fragment.habit.hasReminder()) reminderLabel.setText( + DateUtils.formatTime(fragment.getActivity(), fragment.habit.reminderHour, + fragment.habit.reminderMin)); + else reminderLabel.setText(fragment.getResources().getString(R.string.reminder_off)); + + TextView frequencyLabel = (TextView) view.findViewById(R.id.frequencyLabel); + frequencyLabel.setText(getFreqText()); + + if (fragment.habit.description.isEmpty()) questionLabel.setVisibility(View.GONE); + } + + void updateCardHeaders(View view) + { + updateColor(view, R.id.tvHistory); + updateColor(view, R.id.tvOverview); + updateColor(view, R.id.tvStrength); + updateColor(view, R.id.tvStreaks); + updateColor(view, R.id.tvWeekdayFreq); + updateColor(view, R.id.scoreLabel); + } + + void updateColor(View view, int viewId) + { + if(fragment.habit == null || fragment.activity == null) return; + + TextView textView = (TextView) view.findViewById(viewId); + int androidColor = ColorUtils.getColor(fragment.activity, fragment.habit.color); + textView.setTextColor(androidColor); + } + + void updateColors() + { + fragment.activeColor = ColorUtils.getColor(fragment.getContext(), fragment.habit.color); + fragment.inactiveColor = InterfaceUtils.getStyledColor(fragment.getContext(), + R.attr.mediumContrastTextColor); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java index f6492725a..b19b62bf5 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java @@ -36,7 +36,6 @@ import android.util.TypedValue; import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.Command; import java.util.Locale; @@ -55,11 +54,6 @@ public abstract class InterfaceUtils InterfaceUtils.fixedTheme = fixedTheme; } - public interface OnSavedListener - { - void onSaved(Command command, Object savedObject); - } - public static Typeface getFontAwesome(Context context) { if(fontAwesome == null) diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java index 7cf504508..212aaa570 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java @@ -26,6 +26,8 @@ import android.graphics.RectF; import android.util.AttributeSet; import org.isoron.uhabits.R; +import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; @@ -38,9 +40,8 @@ import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Random; -public class HabitFrequencyView extends ScrollableDataView implements HabitDataView +public class HabitFrequencyView extends ScrollableDataView implements HabitDataView, ModelObservable.Listener { - private Paint pGrid; private float em; private Habit habit; @@ -173,10 +174,12 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV public void refreshData() { - if(isInEditMode()) - generateRandomData(); + if(isInEditMode()) generateRandomData(); else if(habit != null) + { frequency = habit.repetitions.getWeekdayFrequency(); + createColors(); + } postInvalidate(); } @@ -297,4 +300,35 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV this.isBackgroundTransparent = isBackgroundTransparent; createColors(); } + + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + new BaseTask() + { + @Override + protected void doInBackground() + { + refreshData(); + } + }.execute(); + habit.observable.addListener(this); + habit.checkmarks.observable.addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + habit.checkmarks.observable.removeListener(this); + habit.observable.removeListener(this); + super.onDetachedFromWindow(); + } + + @Override + public void onModelChange() + { + refreshData(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index 4c529194e..d390fdb42 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -30,12 +30,13 @@ import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.ModelObservable; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.tasks.ToggleRepetitionTask; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.InterfaceUtils; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -43,7 +44,7 @@ import java.util.GregorianCalendar; import java.util.Random; public class HabitHistoryView extends ScrollableDataView implements HabitDataView, - ToggleRepetitionTask.Listener + ToggleRepetitionTask.Listener, ModelObservable.Listener { private Habit habit; private int[] checkmarks; @@ -216,6 +217,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie { if(habit == null) return; checkmarks = habit.checkmarks.getAllValues(); + createColors(); } updateDate(); @@ -409,4 +411,34 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie } }.execute(); } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + new BaseTask() + { + @Override + protected void doInBackground() + { + refreshData(); + } + }.execute(); + habit.observable.addListener(this); + habit.checkmarks.observable.addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + habit.checkmarks.observable.removeListener(this); + habit.observable.removeListener(this); + super.onDetachedFromWindow(); + } + + @Override + public void onModelChange() + { + refreshData(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java index 29c00e24b..5a336d101 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java @@ -31,18 +31,21 @@ import android.support.annotation.Nullable; import android.util.AttributeSet; import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Random; -public class HabitScoreView extends ScrollableDataView implements HabitDataView +public class HabitScoreView extends ScrollableDataView + implements HabitDataView, ModelObservable.Listener { public static final PorterDuffXfermode XFERMODE_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); @@ -195,6 +198,7 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView { if (habit == null) return; scores = habit.scores.getAllValues(bucketSize); + createColors(); } postInvalidate(); @@ -434,4 +438,34 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView return maxDayWidth; } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + new BaseTask() + { + @Override + protected void doInBackground() + { + refreshData(); + } + }.execute(); + habit.observable.addListener(this); + habit.scores.observable.addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + habit.scores.observable.removeListener(this); + habit.observable.removeListener(this); + super.onDetachedFromWindow(); + } + + @Override + public void onModelChange() + { + refreshData(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java index 27959fddf..6019ef4a1 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java @@ -28,6 +28,8 @@ import android.util.AttributeSet; import android.view.View; import org.isoron.uhabits.R; +import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; @@ -39,7 +41,7 @@ import java.util.Date; import java.util.List; import java.util.TimeZone; -public class HabitStreakView extends View implements HabitDataView +public class HabitStreakView extends View implements HabitDataView, ModelObservable.Listener { private Habit habit; private Paint paint; @@ -151,6 +153,7 @@ public class HabitStreakView extends View implements HabitDataView { if(habit == null) return; streaks = habit.streaks.getAll(maxStreakCount); + createColors(); updateMaxMin(); postInvalidate(); } @@ -246,4 +249,35 @@ public class HabitStreakView extends View implements HabitDataView this.isBackgroundTransparent = isBackgroundTransparent; createColors(); } + + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + new BaseTask() + { + @Override + protected void doInBackground() + { + refreshData(); + } + }.execute(); + habit.observable.addListener(this); + habit.streaks.observable.addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + habit.streaks.observable.removeListener(this); + habit.observable.removeListener(this); + super.onDetachedFromWindow(); + } + + @Override + public void onModelChange() + { + refreshData(); + } } From b98853ab26ef6ed478bd6cdae5af3b66b1e17ab7 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 27 May 2016 23:02:04 -0400 Subject: [PATCH 008/184] Introduce ButterKnife --- app/build.gradle | 3 + .../ui/habits/list/ListHabitsFragment.java | 44 +++---- .../ui/habits/show/ShowHabitFragment.java | 110 +++++++++--------- build.gradle | 1 + 4 files changed, 75 insertions(+), 83 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f8cefba08..8164b62ab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'com.neenbedankt.android-apt' android { compileSdkVersion 23 @@ -40,6 +41,8 @@ dependencies { compile 'org.apmem.tools:layouts:1.10@aar' compile 'com.opencsv:opencsv:3.7' compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT' + compile 'com.jakewharton:butterknife:8.0.1' + apt 'com.jakewharton:butterknife-compiler:8.0.1' compile project(':libs:drag-sort-listview:library') diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java index cefecc0c9..586fb5387 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java @@ -28,7 +28,6 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.ProgressBar; @@ -43,8 +42,12 @@ import org.isoron.uhabits.ui.HintManager; import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.utils.InterfaceUtils; -public class ListHabitsFragment extends Fragment implements OnClickListener, - HabitListSelectionCallback.Listener, ListHabitsController.Screen +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +public class ListHabitsFragment extends Fragment + implements HabitListSelectionCallback.Listener, ListHabitsController.Screen { private ActionMode actionMode; private HintManager hintManager; @@ -52,38 +55,26 @@ public class ListHabitsFragment extends Fragment implements OnClickListener, private Listener habitClickListener; private BaseActivity activity; - private HabitListView listView; - private LinearLayout llButtonsHeader; - private ProgressBar progressBar; - private View llEmpty; + @BindView(R.id.listView) HabitListView listView; + @BindView(R.id.llButtonsHeader) LinearLayout llButtonsHeader; + @BindView(R.id.progressBar) ProgressBar progressBar; + @BindView(R.id.llEmpty) View llEmpty; + @BindView(R.id.llHint) View llHint; + @BindView(R.id.tvStarEmpty) TextView tvStarEmpty; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.list_habits_fragment, container, false); - - View llHint = view.findViewById(R.id.llHint); - llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader); - llEmpty = view.findViewById(R.id.llEmpty); - progressBar = (ProgressBar) view.findViewById(R.id.progressBar); - listView = (HabitListView) view.findViewById(R.id.listView); - TextView tvStarEmpty = (TextView) view.findViewById(R.id.tvStarEmpty); + ButterKnife.bind(this, view); helper = new ListHabitsHelper(activity, listView.getLoader()); hintManager = new HintManager(activity, llHint); - - llHint.setOnClickListener(this); tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(activity)); listView.setListener(new HabitListViewListener()); setHasOptionsMenu(true); - if(savedInstanceState != null) - { - EditHabitDialogFragment frag = (EditHabitDialogFragment) getFragmentManager() - .findFragmentByTag("editHabit"); - } - return view; } @@ -100,7 +91,6 @@ public class ListHabitsFragment extends Fragment implements OnClickListener, public void onResume() { super.onResume(); - listView.getLoader().onResume(); listView.refreshData(null); helper.updateEmptyMessage(llEmpty); @@ -124,6 +114,7 @@ public class ListHabitsFragment extends Fragment implements OnClickListener, showArchivedItem.setChecked(listView.getShowArchived()); } + @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -175,11 +166,10 @@ public class ListHabitsFragment extends Fragment implements OnClickListener, listView.cancelSelection(); } - @Override - public void onClick(View v) + @OnClick(R.id.llHint) + public void onClickHint() { - if (v.getId() == R.id.llHint) - hintManager.dismissHint(); + hintManager.dismissHint(); } public ProgressBar getProgressBar() diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 3ce2bf349..093d6d51f 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -20,7 +20,6 @@ package org.isoron.uhabits.ui.habits.show; import android.os.Bundle; -import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.Menu; @@ -29,7 +28,6 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.Button; import android.widget.Spinner; import org.isoron.uhabits.R; @@ -49,15 +47,12 @@ import org.isoron.uhabits.views.HabitStreakView; import java.util.LinkedList; import java.util.List; -public class ShowHabitFragment extends Fragment - implements Spinner.OnItemSelectedListener, ModelObservable.Listener -{ - protected ShowHabitActivity activity; - @Nullable - private List dataViews; - - private int previousScoreInterval; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +public class ShowHabitFragment extends Fragment implements ModelObservable.Listener +{ Habit habit; float todayScore; @@ -65,14 +60,25 @@ public class ShowHabitFragment extends Fragment float lastYearScore; int activeColor; int inactiveColor; + int previousScoreInterval; private ShowHabitHelper helper; + protected ShowHabitActivity activity; + private List dataViews; + + @BindView(R.id.sStrengthInterval) Spinner sStrengthInterval; + @BindView(R.id.scoreView) HabitScoreView habitScoreView; + @BindView(R.id.historyView) HabitHistoryView habitHistoryView; + @BindView(R.id.punchcardView) HabitFrequencyView habitFrequencyView; + @BindView(R.id.streakView) HabitStreakView habitStreakView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.show_habit, container, false); + ButterKnife.bind(this, view); + activity = (ShowHabitActivity) getActivity(); helper = new ShowHabitHelper(this); @@ -84,41 +90,31 @@ public class ShowHabitFragment extends Fragment previousScoreInterval = defaultScoreInterval; setScoreBucketSize(defaultScoreInterval); - Spinner sStrengthInterval = (Spinner) view.findViewById(R.id.sStrengthInterval); sStrengthInterval.setSelection(defaultScoreInterval); - sStrengthInterval.setOnItemSelectedListener(this); + sStrengthInterval.setOnItemSelectedListener(new OnItemSelectedListener()); - createDataViews(view); + createDataViews(); helper.updateCardHeaders(view); - - bindButtontEditHistory(view); setHasOptionsMenu(true); return view; } - private void bindButtontEditHistory(View view) + @OnClick(R.id.btEditHistory) + public void onClickEditHistory() { - Button btEditHistory = (Button) view.findViewById(R.id.btEditHistory); - btEditHistory.setOnClickListener(new View.OnClickListener() - { - @Override - public void onClick(View v) - { - HistoryEditorDialog frag = new HistoryEditorDialog(); - frag.setHabit(habit); - frag.show(getFragmentManager(), "historyEditor"); - } - }); + HistoryEditorDialog frag = new HistoryEditorDialog(); + frag.setHabit(habit); + frag.show(getFragmentManager(), "historyEditor"); } - private void createDataViews(View view) + private void createDataViews() { dataViews = new LinkedList<>(); - dataViews.add((HabitScoreView) view.findViewById(R.id.scoreView)); - dataViews.add((HabitHistoryView) view.findViewById(R.id.historyView)); - dataViews.add((HabitFrequencyView) view.findViewById(R.id.punchcardView)); - dataViews.add((HabitStreakView) view.findViewById(R.id.streakView)); + dataViews.add(habitScoreView); + dataViews.add(habitHistoryView); + dataViews.add(habitFrequencyView); + dataViews.add(habitStreakView); for(HabitDataView dataView : dataViews) dataView.setHabit(habit); @@ -160,19 +156,11 @@ public class ShowHabitFragment extends Fragment new RefreshTask().execute(); } - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) - { - if(parent.getId() == R.id.sStrengthInterval) - setScoreBucketSize(position); - } - private void setScoreBucketSize(int position) { if(getView() == null) return; - HabitScoreView scoreView = (HabitScoreView) getView().findViewById(R.id.scoreView); - scoreView.setBucketSize(HabitScoreView.DEFAULT_BUCKET_SIZES[position]); + habitScoreView.setBucketSize(HabitScoreView.DEFAULT_BUCKET_SIZES[position]); if(position != previousScoreInterval) refreshData(); @@ -181,11 +169,6 @@ public class ShowHabitFragment extends Fragment previousScoreInterval = position; } - @Override - public void onNothingSelected(AdapterView parent) - { - } - @Override public void onModelChange() { @@ -203,6 +186,20 @@ public class ShowHabitFragment extends Fragment }); } + @Override + public void onStart() + { + super.onStart(); + habit.observable.addListener(this); + } + + @Override + public void onPause() + { + habit.observable.removeListener(this); + super.onPause(); + } + private class RefreshTask extends BaseTask { @Override @@ -228,17 +225,18 @@ public class ShowHabitFragment extends Fragment } } - @Override - public void onStart() + private class OnItemSelectedListener implements AdapterView.OnItemSelectedListener { - super.onStart(); - habit.observable.addListener(this); - } + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) + { + setScoreBucketSize(position); + } - @Override - public void onPause() - { - habit.observable.removeListener(this); - super.onPause(); + @Override + public void onNothingSelected(AdapterView parent) + { + + } } } diff --git a/build.gradle b/build.gradle index eb0a985ee..d9ea1210d 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } From 8d57273987ce037c9516016fd5d87e4db066e730 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 28 May 2016 10:15:55 -0400 Subject: [PATCH 009/184] Refactor EditHabitDialog --- .../uhabits/ui/MainActivityActions.java | 12 +- .../java/org/isoron/uhabits/ui/MainTest.java | 2 +- .../ui/habits/edit/BaseDialogFragment.java | 212 +++++++++ .../ui/habits/edit/BaseDialogHelper.java | 161 +++++++ .../edit/CreateHabitDialogFragment.java | 60 +++ .../habits/edit/EditHabitDialogFragment.java | 410 +----------------- .../list/HabitListSelectionCallback.java | 5 +- .../ui/habits/list/ListHabitsFragment.java | 5 +- .../ui/habits/show/ShowHabitFragment.java | 3 +- .../org/isoron/uhabits/utils/Preferences.java | 11 + app/src/main/res/layout/edit_habit.xml | 14 +- 11 files changed, 483 insertions(+), 412 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java index a933cd3a9..7da8081ef 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java @@ -80,11 +80,11 @@ public class MainActivityActions onView(withId(R.id.buttonPickColor)) .perform(click()); pressBack(); - onView(withId(R.id.inputReminderTime)) + onView(withId(R.id.tvReminderTime)) .perform(click()); onView(withText("Done")) .perform(click()); - onView(withId(R.id.inputReminderDays)) + onView(withId(R.id.tvReminderDays)) .perform(click()); onView(withText("OK")) .perform(click()); @@ -101,9 +101,9 @@ public class MainActivityActions public static void typeHabitData(String name, String description, String num, String den) { - onView(withId(R.id.input_name)) + onView(withId(R.id.tvName)) .perform(replaceText(name)); - onView(withId(R.id.input_description)) + onView(withId(R.id.tvDescription)) .perform(replaceText(description)); try @@ -119,9 +119,9 @@ public class MainActivityActions // ignored } - onView(withId(R.id.input_freq_num)) + onView(withId(R.id.tvFreqNum)) .perform(replaceText(num)); - onView(withId(R.id.input_freq_den)) + onView(withId(R.id.tvFreqDen)) .perform(replaceText(den)); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index e26fd2f17..fe6d71d28 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -178,7 +178,7 @@ public class MainTest typeHabitData("", "", "15", "7"); onView(withId(R.id.buttonSave)).perform(click()); - onView(withId(R.id.input_name)).check(matches(isDisplayed())); + onView(withId(R.id.tvName)).check(matches(isDisplayed())); } /** diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java new file mode 100644 index 000000000..5be97cbe8 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java @@ -0,0 +1,212 @@ +/* + * 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.ui.habits.edit; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatDialogFragment; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.colorpicker.ColorPickerDialog; +import com.android.colorpicker.ColorPickerSwatch; +import com.android.datetimepicker.time.RadialPickerLayout; +import com.android.datetimepicker.time.TimePickerDialog; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.Preferences; + +import java.util.Arrays; + +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnItemSelected; + +public abstract class BaseDialogFragment extends AppCompatDialogFragment +{ + protected Habit originalHabit; + protected Habit modifiedHabit; + protected Preferences prefs = Preferences.getInstance(); + protected BaseDialogHelper helper; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.edit_habit, container, false); + helper = new BaseDialogHelper(this, view); + ButterKnife.bind(this, view); + return view; + } + + @Override + @SuppressWarnings("ConstantConditions") + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putInt("color", modifiedHabit.color); + if(modifiedHabit.hasReminder()) + { + outState.putInt("reminderMin", modifiedHabit.reminderMin); + outState.putInt("reminderHour", modifiedHabit.reminderHour); + outState.putInt("reminderDays", modifiedHabit.reminderDays); + } + } + + protected void restoreSavedInstance(@Nullable Bundle bundle) + { + if(bundle == null) return; + modifiedHabit.color = bundle.getInt("color", modifiedHabit.color); + modifiedHabit.reminderMin = bundle.getInt("reminderMin", -1); + modifiedHabit.reminderHour = bundle.getInt("reminderHour", -1); + modifiedHabit.reminderDays = bundle.getInt("reminderDays", -1); + if(modifiedHabit.reminderMin < 0) modifiedHabit.clearReminder(); + } + + @OnClick(R.id.buttonDiscard) + void onButtonDiscardClick() + { + dismiss(); + } + + @OnClick(R.id.buttonSave) + void onSaveButtonClick() + { + helper.parseFormIntoHabit(modifiedHabit); + if (!helper.validate(modifiedHabit)) return; + saveHabit(); + dismiss(); + } + + @OnClick(R.id.tvReminderTime) + @SuppressWarnings("ConstantConditions") + void onDateSpinnerClick() + { + int defaultHour = 8; + int defaultMin = 0; + + if (modifiedHabit.hasReminder()) + { + defaultHour = modifiedHabit.reminderHour; + defaultMin = modifiedHabit.reminderMin; + } + + showTimePicker(defaultHour, defaultMin); + } + + @OnClick(R.id.tvReminderDays) + @SuppressWarnings("ConstantConditions") + void onWeekdayClick() + { + if(!modifiedHabit.hasReminder()) return; + WeekdayPickerDialog dialog = new WeekdayPickerDialog(); + dialog.setListener(new OnWeekdaysPickedListener()); + dialog.setSelectedDays(DateUtils.unpackWeekdayList(modifiedHabit.reminderDays)); + dialog.show(getFragmentManager(), "weekdayPicker"); + } + + @OnItemSelected(R.id.sFrequency) + public void onFrequencySelected(int position) + { + if(position < 0 || position > 4) throw new IllegalArgumentException(); + int freqNums[] = { 1, 1, 2, 5, 3 }; + int freqDens[] = { 1, 7, 7, 7, 7 }; + modifiedHabit.freqNum = freqNums[position]; + modifiedHabit.freqDen = freqDens[position]; + helper.populateFrequencyFields(modifiedHabit); + } + + protected abstract void saveHabit(); + + @OnClick(R.id.buttonPickColor) + void showColorPicker() + { + int androidColor = ColorUtils.getColor(getContext(), modifiedHabit.color); + + ColorPickerDialog picker = ColorPickerDialog.newInstance( + R.string.color_picker_default_title, ColorUtils.getPalette(getContext()), + androidColor, 4, ColorPickerDialog.SIZE_SMALL); + + picker.setOnColorSelectedListener(new OnColorSelectedListener()); + picker.show(getFragmentManager(), "picker"); + } + + private void showTimePicker(int defaultHour, int defaultMin) + { + boolean is24HourMode = DateFormat.is24HourFormat(getContext()); + TimePickerDialog timePicker = TimePickerDialog.newInstance(new OnTimeSetListener(), + defaultHour, defaultMin, is24HourMode); + timePicker.show(getFragmentManager(), "timePicker"); + } + + private class OnColorSelectedListener implements ColorPickerSwatch.OnColorSelectedListener + { + public void onColorSelected(int androidColor) + { + int paletteColor = ColorUtils.colorToPaletteIndex(getActivity(), androidColor); + prefs.setDefaultHabitColor(paletteColor); + modifiedHabit.color = paletteColor; + helper.populateColor(paletteColor); + } + } + + private class OnWeekdaysPickedListener implements WeekdayPickerDialog.OnWeekdaysPickedListener + { + @Override + public void onWeekdaysPicked(boolean[] selectedDays) + { + if(isSelectionEmpty(selectedDays)) + Arrays.fill(selectedDays, true); + + modifiedHabit.reminderDays = DateUtils.packWeekdayList(selectedDays); + helper.populateReminderFields(modifiedHabit); + } + + private boolean isSelectionEmpty(boolean[] selectedDays) + { + for (boolean d : selectedDays) if (d) return false; + return true; + } + } + + private class OnTimeSetListener implements TimePickerDialog.OnTimeSetListener + { + @Override + public void onTimeSet(RadialPickerLayout view, int hour, int minute) + { + modifiedHabit.reminderHour = hour; + modifiedHabit.reminderMin = minute; + modifiedHabit.reminderDays = DateUtils.ALL_WEEK_DAYS; + helper.populateReminderFields(modifiedHabit); + } + + @Override + public void onTimeCleared(RadialPickerLayout view) + { + modifiedHabit.clearReminder(); + helper.populateReminderFields(modifiedHabit); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java new file mode 100644 index 000000000..7605976c2 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java @@ -0,0 +1,161 @@ +/* + * 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.ui.habits.edit; + +import android.annotation.SuppressLint; +import android.support.v4.app.DialogFragment; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Spinner; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.DateUtils; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class BaseDialogHelper +{ + private DialogFragment frag; + @BindView(R.id.tvName) TextView tvName; + @BindView(R.id.tvDescription) TextView tvDescription; + @BindView(R.id.tvFreqNum) TextView tvFreqNum; + @BindView(R.id.tvFreqDen) TextView tvFreqDen; + @BindView(R.id.tvReminderTime) TextView tvReminderTime; + @BindView(R.id.tvReminderDays) TextView tvReminderDays; + @BindView(R.id.sFrequency) Spinner sFrequency; + @BindView(R.id.llCustomFrequency) ViewGroup llCustomFrequency; + @BindView(R.id.llReminderDays) ViewGroup llReminderDays; + + public BaseDialogHelper(DialogFragment frag, View view) + { + this.frag = frag; + ButterKnife.bind(this, view); + } + + protected void populateForm(final Habit habit) + { + if(habit.name != null) tvName.setText(habit.name); + if(habit.description != null) tvDescription.setText(habit.description); + + populateColor(habit.color); + populateFrequencyFields(habit); + populateReminderFields(habit); + } + + void populateColor(int paletteColor) + { + tvName.setTextColor(ColorUtils.getColor(frag.getContext(), paletteColor)); + } + + @SuppressWarnings("ConstantConditions") + void populateReminderFields(Habit habit) + { + if (!habit.hasReminder()) + { + tvReminderTime.setText(R.string.reminder_off); + llReminderDays.setVisibility(View.GONE); + return; + } + + String time = DateUtils.formatTime(frag.getContext(), habit.reminderHour, habit.reminderMin); + tvReminderTime.setText(time); + llReminderDays.setVisibility(View.VISIBLE); + + boolean weekdays[] = DateUtils.unpackWeekdayList(habit.reminderDays); + tvReminderDays.setText(DateUtils.formatWeekdayList(frag.getContext(), weekdays)); + } + + @SuppressLint("SetTextI18n") + void populateFrequencyFields(Habit habit) + { + int quickSelectPosition = -1; + + if(habit.freqNum.equals(habit.freqDen)) + quickSelectPosition = 0; + + else if(habit.freqNum == 1 && habit.freqDen == 7) + quickSelectPosition = 1; + + else if(habit.freqNum == 2 && habit.freqDen == 7) + quickSelectPosition = 2; + + else if(habit.freqNum == 5 && habit.freqDen == 7) + quickSelectPosition = 3; + + if(quickSelectPosition >= 0) showSimplifiedFrequency(quickSelectPosition); + else showCustomFrequency(); + + tvFreqNum.setText(habit.freqNum.toString()); + tvFreqDen.setText(habit.freqDen.toString()); + } + + private void showCustomFrequency() + { + sFrequency.setVisibility(View.GONE); + llCustomFrequency.setVisibility(View.VISIBLE); + } + + @SuppressLint("SetTextI18n") + private void showSimplifiedFrequency(int quickSelectPosition) + { + sFrequency.setVisibility(View.VISIBLE); + sFrequency.setSelection(quickSelectPosition); + llCustomFrequency.setVisibility(View.GONE); + } + + boolean validate(Habit habit) + { + Boolean valid = true; + + if (habit.name.length() == 0) + { + tvName.setError(frag.getString(R.string.validation_name_should_not_be_blank)); + valid = false; + } + + if (habit.freqNum <= 0) + { + tvFreqNum.setError(frag.getString(R.string.validation_number_should_be_positive)); + valid = false; + } + + if (habit.freqNum > habit.freqDen) + { + tvFreqNum.setError(frag.getString(R.string.validation_at_most_one_rep_per_day)); + valid = false; + } + + return valid; + } + + void parseFormIntoHabit(Habit habit) + { + habit.name = tvName.getText().toString().trim(); + habit.description = tvDescription.getText().toString().trim(); + String freqNum = tvFreqNum.getText().toString(); + String freqDen = tvFreqDen.getText().toString(); + if(!freqNum.isEmpty()) habit.freqNum = Integer.parseInt(freqNum); + if(!freqDen.isEmpty()) habit.freqDen = Integer.parseInt(freqDen); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java new file mode 100644 index 000000000..8cd92750d --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java @@ -0,0 +1,60 @@ +/* + * 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.ui.habits.edit; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.commands.Command; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.commands.CreateHabitCommand; +import org.isoron.uhabits.models.Habit; + +public class CreateHabitDialogFragment extends BaseDialogFragment +{ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = super.onCreateView(inflater, container, savedInstanceState); + getDialog().setTitle(R.string.create_habit); + initializeHabits(); + restoreSavedInstance(savedInstanceState); + helper.populateForm(modifiedHabit); + return view; + } + + private void initializeHabits() + { + modifiedHabit = new Habit(); + modifiedHabit.freqNum = 1; + modifiedHabit.freqDen = 1; + modifiedHabit.color = prefs.getDefaultHabitColor(modifiedHabit.color); + } + + protected void saveHabit() + { + Command command = new CreateHabitCommand(modifiedHabit); + CommandRunner.getInstance().execute(command, null); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java index 9a84b84b3..413a298cb 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java @@ -19,428 +19,52 @@ package org.isoron.uhabits.ui.habits.edit; -import android.annotation.SuppressLint; -import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.v7.app.AppCompatDialogFragment; -import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.Spinner; -import android.widget.TextView; - -import com.android.colorpicker.ColorPickerDialog; -import com.android.colorpicker.ColorPickerSwatch; -import com.android.datetimepicker.time.RadialPickerLayout; -import com.android.datetimepicker.time.TimePickerDialog; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.commands.EditHabitCommand; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; - -import java.util.Arrays; -public class EditHabitDialogFragment extends AppCompatDialogFragment - implements OnClickListener, WeekdayPickerDialog.OnWeekdaysPickedListener, - TimePickerDialog.OnTimeSetListener, Spinner.OnItemSelectedListener +public class EditHabitDialogFragment extends BaseDialogFragment { - private Integer mode; - static final int EDIT_MODE = 0; - static final int CREATE_MODE = 1; - - private Habit originalHabit; - private Habit modifiedHabit; - - private TextView tvName; - private TextView tvDescription; - private TextView tvFreqNum; - private TextView tvFreqDen; - private TextView tvReminderTime; - private TextView tvReminderDays; - - private Spinner sFrequency; - private ViewGroup llCustomFrequency; - private ViewGroup llReminderDays; - - private SharedPreferences prefs; - private boolean is24HourMode; - - public static EditHabitDialogFragment editSingleHabitFragment(long id) - { - EditHabitDialogFragment frag = new EditHabitDialogFragment(); - Bundle args = new Bundle(); - args.putLong("habitId", id); - args.putInt("editMode", EDIT_MODE); - frag.setArguments(args); - return frag; - } - - public static EditHabitDialogFragment createHabitFragment() + public static EditHabitDialogFragment newInstance(long habitId) { EditHabitDialogFragment frag = new EditHabitDialogFragment(); Bundle args = new Bundle(); - args.putInt("editMode", CREATE_MODE); + args.putLong("habitId", habitId); frag.setArguments(args); return frag; } - @Override + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.edit_habit, container, false); - tvName = (TextView) view.findViewById(R.id.input_name); - tvDescription = (TextView) view.findViewById(R.id.input_description); - tvFreqNum = (TextView) view.findViewById(R.id.input_freq_num); - tvFreqDen = (TextView) view.findViewById(R.id.input_freq_den); - tvReminderTime = (TextView) view.findViewById(R.id.inputReminderTime); - tvReminderDays = (TextView) view.findViewById(R.id.inputReminderDays); - - sFrequency = (Spinner) view.findViewById(R.id.sFrequency); - llCustomFrequency = (ViewGroup) view.findViewById(R.id.llCustomFrequency); - llReminderDays = (ViewGroup) view.findViewById(R.id.llReminderDays); - - Button buttonSave = (Button) view.findViewById(R.id.buttonSave); - Button buttonDiscard = (Button) view.findViewById(R.id.buttonDiscard); - ImageButton buttonPickColor = (ImageButton) view.findViewById(R.id.buttonPickColor); - - buttonSave.setOnClickListener(this); - buttonDiscard.setOnClickListener(this); - tvReminderTime.setOnClickListener(this); - tvReminderDays.setOnClickListener(this); - buttonPickColor.setOnClickListener(this); - sFrequency.setOnItemSelectedListener(this); - - prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); - - Bundle args = getArguments(); - mode = (Integer) args.get("editMode"); - - is24HourMode = DateFormat.is24HourFormat(getActivity()); - - if (mode == CREATE_MODE) - { - getDialog().setTitle(R.string.create_habit); - modifiedHabit = new Habit(); - modifiedHabit.freqNum = 1; - modifiedHabit.freqDen = 1; - modifiedHabit.color = prefs.getInt("pref_default_habit_palette_color", modifiedHabit.color); - } - else if (mode == EDIT_MODE) - { - Long habitId = (Long) args.get("habitId"); - if(habitId == null) throw new IllegalArgumentException("habitId must be specified"); - - originalHabit = Habit.get(habitId); - modifiedHabit = new Habit(originalHabit); - - getDialog().setTitle(R.string.edit_habit); - tvName.append(modifiedHabit.name); - tvDescription.append(modifiedHabit.description); - } - - if(savedInstanceState != null) - { - modifiedHabit.color = savedInstanceState.getInt("color", modifiedHabit.color); - modifiedHabit.reminderMin = savedInstanceState.getInt("reminderMin", -1); - modifiedHabit.reminderHour = savedInstanceState.getInt("reminderHour", -1); - modifiedHabit.reminderDays = savedInstanceState.getInt("reminderDays", -1); - - if(modifiedHabit.reminderMin < 0) - modifiedHabit.clearReminder(); - } - - tvFreqNum.append(modifiedHabit.freqNum.toString()); - tvFreqDen.append(modifiedHabit.freqDen.toString()); - - changeColor(modifiedHabit.color); - updateFrequency(); - updateReminder(); - + View view = super.onCreateView(inflater, container, savedInstanceState); + getDialog().setTitle(R.string.edit_habit); + initializeHabits(); + restoreSavedInstance(savedInstanceState); + helper.populateForm(modifiedHabit); return view; } - private void changeColor(int paletteColor) - { - modifiedHabit.color = paletteColor; - tvName.setTextColor(ColorUtils.getColor(getActivity(), paletteColor)); - - SharedPreferences.Editor editor = prefs.edit(); - editor.putInt("pref_default_habit_palette_color", paletteColor); - editor.apply(); - } - - @SuppressWarnings("ConstantConditions") - private void updateReminder() - { - if (modifiedHabit.hasReminder()) - { - tvReminderTime.setText(DateUtils.formatTime(getActivity(), modifiedHabit.reminderHour, - modifiedHabit.reminderMin)); - llReminderDays.setVisibility(View.VISIBLE); - - boolean weekdays[] = DateUtils.unpackWeekdayList(modifiedHabit.reminderDays); - tvReminderDays.setText(DateUtils.formatWeekdayList(getActivity(), weekdays)); - } - else - { - tvReminderTime.setText(R.string.reminder_off); - llReminderDays.setVisibility(View.GONE); - } - } - - @Override - public void onClick(View v) - { - switch(v.getId()) - { - case R.id.inputReminderTime: - onDateSpinnerClick(); - break; - - case R.id.inputReminderDays: - onWeekdayClick(); - break; - - case R.id.buttonSave: - onSaveButtonClick(); - break; - - case R.id.buttonDiscard: - dismiss(); - break; - - case R.id.buttonPickColor: - onColorButtonClick(); - break; - } - } - - private void onColorButtonClick() - { - int originalAndroidColor = ColorUtils.getColor(getActivity(), modifiedHabit.color); - - ColorPickerDialog picker = ColorPickerDialog.newInstance( - R.string.color_picker_default_title, ColorUtils.getPalette(getActivity()), - originalAndroidColor, 4, ColorPickerDialog.SIZE_SMALL); - - picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() - { - public void onColorSelected(int androidColor) - { - int paletteColor = ColorUtils.colorToPaletteIndex(getActivity(), androidColor); - changeColor(paletteColor); - } - }); - picker.show(getFragmentManager(), "picker"); - } - - private void onSaveButtonClick() - { - modifiedHabit.name = tvName.getText().toString().trim(); - modifiedHabit.description = tvDescription.getText().toString().trim(); - String freqNum = tvFreqNum.getText().toString(); - String freqDen = tvFreqDen.getText().toString(); - if(!freqNum.isEmpty()) modifiedHabit.freqNum = Integer.parseInt(freqNum); - if(!freqDen.isEmpty()) modifiedHabit.freqDen = Integer.parseInt(freqDen); - - if (!validate()) return; - - if (mode == EDIT_MODE) - { - Command command = new EditHabitCommand(originalHabit, modifiedHabit); - CommandRunner.getInstance().execute(command, originalHabit.getId()); - } - else if (mode == CREATE_MODE) - { - Command command = new CreateHabitCommand(modifiedHabit); - CommandRunner.getInstance().execute(command, null); - } - - dismiss(); - } - - private boolean validate() - { - Boolean valid = true; - - if (modifiedHabit.name.length() == 0) - { - tvName.setError(getString(R.string.validation_name_should_not_be_blank)); - valid = false; - } - - if (modifiedHabit.freqNum <= 0) - { - tvFreqNum.setError(getString(R.string.validation_number_should_be_positive)); - valid = false; - } - - if (modifiedHabit.freqNum > modifiedHabit.freqDen) - { - tvFreqNum.setError(getString(R.string.validation_at_most_one_rep_per_day)); - valid = false; - } - - return valid; - } - - @SuppressWarnings("ConstantConditions") - private void onDateSpinnerClick() + private void initializeHabits() { - int defaultHour = 8; - int defaultMin = 0; + Long habitId = (Long) getArguments().get("habitId"); + if(habitId == null) throw new IllegalArgumentException("habitId must be specified"); - if (modifiedHabit.hasReminder()) - { - defaultHour = modifiedHabit.reminderHour; - defaultMin = modifiedHabit.reminderMin; - } - - TimePickerDialog timePicker = - TimePickerDialog.newInstance(this, defaultHour, defaultMin, is24HourMode); - timePicker.show(getFragmentManager(), "timePicker"); + originalHabit = Habit.get(habitId); + modifiedHabit = new Habit(originalHabit); } - @SuppressWarnings("ConstantConditions") - private void onWeekdayClick() + protected void saveHabit() { - if(!modifiedHabit.hasReminder()) return; - - WeekdayPickerDialog dialog = new WeekdayPickerDialog(); - dialog.setListener(this); - dialog.setSelectedDays(DateUtils.unpackWeekdayList(modifiedHabit.reminderDays)); - dialog.show(getFragmentManager(), "weekdayPicker"); - } - - @Override - public void onTimeSet(RadialPickerLayout view, int hour, int minute) - { - modifiedHabit.reminderHour = hour; - modifiedHabit.reminderMin = minute; - modifiedHabit.reminderDays = DateUtils.ALL_WEEK_DAYS; - updateReminder(); - } - - @Override - public void onTimeCleared(RadialPickerLayout view) - { - modifiedHabit.clearReminder(); - updateReminder(); - } - - @Override - public void onWeekdaysPicked(boolean[] selectedDays) - { - int count = 0; - for(int i = 0; i < 7; i++) - if(selectedDays[i]) count++; - - if(count == 0) Arrays.fill(selectedDays, true); - - modifiedHabit.reminderDays = DateUtils.packWeekdayList(selectedDays); - updateReminder(); - } - - @Override - @SuppressWarnings("ConstantConditions") - public void onSaveInstanceState(Bundle outState) - { - super.onSaveInstanceState(outState); - - outState.putInt("color", modifiedHabit.color); - - if(modifiedHabit.hasReminder()) - { - outState.putInt("reminderMin", modifiedHabit.reminderMin); - outState.putInt("reminderHour", modifiedHabit.reminderHour); - outState.putInt("reminderDays", modifiedHabit.reminderDays); - } - } - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) - { - if(parent.getId() == R.id.sFrequency) - { - switch (position) - { - case 0: - modifiedHabit.freqNum = 1; - modifiedHabit.freqDen = 1; - break; - - case 1: - modifiedHabit.freqNum = 1; - modifiedHabit.freqDen = 7; - break; - - case 2: - modifiedHabit.freqNum = 2; - modifiedHabit.freqDen = 7; - break; - - case 3: - modifiedHabit.freqNum = 5; - modifiedHabit.freqDen = 7; - break; - - case 4: - modifiedHabit.freqNum = 3; - modifiedHabit.freqDen = 7; - break; - } - } - - updateFrequency(); - } - - @SuppressLint("SetTextI18n") - private void updateFrequency() - { - int quickSelectPosition = -1; - - if(modifiedHabit.freqNum.equals(modifiedHabit.freqDen)) - quickSelectPosition = 0; - - else if(modifiedHabit.freqNum == 1 && modifiedHabit.freqDen == 7) - quickSelectPosition = 1; - - else if(modifiedHabit.freqNum == 2 && modifiedHabit.freqDen == 7) - quickSelectPosition = 2; - - else if(modifiedHabit.freqNum == 5 && modifiedHabit.freqDen == 7) - quickSelectPosition = 3; - - if(quickSelectPosition >= 0) - { - sFrequency.setVisibility(View.VISIBLE); - sFrequency.setSelection(quickSelectPosition); - llCustomFrequency.setVisibility(View.GONE); - tvFreqNum.setText(modifiedHabit.freqNum.toString()); - tvFreqDen.setText(modifiedHabit.freqDen.toString()); - } - else - { - sFrequency.setVisibility(View.GONE); - llCustomFrequency.setVisibility(View.VISIBLE); - } - } - - @Override - public void onNothingSelected(AdapterView parent) - { - + Command command = new EditHabitCommand(originalHabit, modifiedHabit); + CommandRunner.getInstance().execute(command, originalHabit.getId()); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java index 1091841e6..f292ff978 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java @@ -36,6 +36,7 @@ import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.utils.ColorUtils; @@ -205,8 +206,8 @@ public class HabitListSelectionCallback implements ActionMode.Callback private void editHabit(Habit habit) { - EditHabitDialogFragment - frag = EditHabitDialogFragment.editSingleHabitFragment(habit.getId()); + BaseDialogFragment + frag = EditHabitDialogFragment.newInstance(habit.getId()); frag.show(activity.getSupportFragmentManager(), "editHabit"); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java index 586fb5387..ce7b6d541 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java @@ -39,7 +39,8 @@ import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.ui.HintManager; -import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; +import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; +import org.isoron.uhabits.ui.habits.edit.CreateHabitDialogFragment; import org.isoron.uhabits.utils.InterfaceUtils; import butterknife.BindView; @@ -141,7 +142,7 @@ public class ListHabitsFragment extends Fragment private void showCreateHabitScreen() { - EditHabitDialogFragment frag = EditHabitDialogFragment.createHabitFragment(); + BaseDialogFragment frag = new CreateHabitDialogFragment(); frag.show(getFragmentManager(), "editHabit"); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 093d6d51f..f088b0d33 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -34,6 +34,7 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.ModelObservable; import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.ui.habits.edit.HistoryEditorDialog; import org.isoron.uhabits.utils.DateUtils; @@ -146,7 +147,7 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste { if(habit == null) return false; - EditHabitDialogFragment frag = EditHabitDialogFragment.editSingleHabitFragment(habit.getId()); + BaseDialogFragment frag = EditHabitDialogFragment.newInstance(habit.getId()); frag.show(getFragmentManager(), "editHabit"); return true; } diff --git a/app/src/main/java/org/isoron/uhabits/utils/Preferences.java b/app/src/main/java/org/isoron/uhabits/utils/Preferences.java index d9d216676..d4d318294 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/Preferences.java +++ b/app/src/main/java/org/isoron/uhabits/utils/Preferences.java @@ -82,4 +82,15 @@ public class Preferences { return prefs.getBoolean("pref_short_toggle", false); } + + public Integer getDefaultHabitColor(int defaultColor) + { + return prefs.getInt("pref_default_habit_palette_color", defaultColor); + } + + public void setDefaultHabitColor(int color) + { + prefs.edit().putInt("pref_default_habit_palette_color", color).apply(); + } + } diff --git a/app/src/main/res/layout/edit_habit.xml b/app/src/main/res/layout/edit_habit.xml index 0643359de..e744f58e5 100644 --- a/app/src/main/res/layout/edit_habit.xml +++ b/app/src/main/res/layout/edit_habit.xml @@ -22,7 +22,7 @@ style="@style/dialogForm" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - tools:context=".ui.habits.edit.EditHabitDialogFragment" + tools:context=".ui.habits.edit.BaseDialogFragment" tools:ignore="MergeRootFrame"> @@ -49,7 +49,7 @@ @@ -78,7 +78,7 @@ android:gravity="fill"> @@ -125,7 +125,7 @@ android:text=""/> From 3b56d6d596d957836de3bcd45b83cdca2fab56c7 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 28 May 2016 10:16:21 -0400 Subject: [PATCH 010/184] Add missing file --- .../uhabits/models/ModelObservable.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 app/src/main/java/org/isoron/uhabits/models/ModelObservable.java diff --git a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java new file mode 100644 index 000000000..a8f12cab7 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java @@ -0,0 +1,54 @@ +/* + * 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; + +import java.util.LinkedList; +import java.util.List; + +public class ModelObservable +{ + List listeners; + + public ModelObservable() + { + super(); + listeners = new LinkedList<>(); + } + + public interface Listener + { + void onModelChange(); + } + + public void addListener(Listener l) + { + listeners.add(l); + } + + public void removeListener(Listener l) + { + listeners.remove(l); + } + + void notifyListeners() + { + for(Listener l : listeners) l.onModelChange(); + } +} From 0ad0e5cf36033c69e2d1da0a472abed2cddf68fe Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 28 May 2016 12:20:02 -0400 Subject: [PATCH 011/184] Fix time out on tests --- .../main/java/org/isoron/uhabits/commands/CommandRunner.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java index 05d541dbc..ab8a55181 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java +++ b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java @@ -59,6 +59,8 @@ public class CommandRunner { for(Listener l : listeners) l.onCommandExecuted(command, refreshKey); + + super.onPostExecute(null); } }.execute(); } From bb950d61fc599f10de3c4f60bdbaab6e8a19f07d Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 28 May 2016 12:21:38 -0400 Subject: [PATCH 012/184] Move sendFile and sendEmail to Screen --- .../org/isoron/uhabits/HabitsApplication.java | 22 ---------------- .../org/isoron/uhabits/MainController.java | 14 +++++------ .../org/isoron/uhabits/ui/BaseActivity.java | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index ab69642b4..9186d5e25 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -21,8 +21,6 @@ package org.isoron.uhabits; import android.app.Application; import android.content.Context; -import android.content.Intent; -import android.net.Uri; import android.os.Environment; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -186,26 +184,6 @@ public class HabitsApplication extends Application implements MainController.Sys return deviceInfo + "\n" + logcat; } - public void sendFile(@NonNull String archiveFilename) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); - startActivity(intent); - } - - public void sendEmail(String to, String subject, String content) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("message/rfc822"); - intent.putExtra(Intent.EXTRA_EMAIL, new String[] {to}); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, content); - startActivity(intent); - } - public void scheduleReminders() { new BaseTask() diff --git a/app/src/main/java/org/isoron/uhabits/MainController.java b/app/src/main/java/org/isoron/uhabits/MainController.java index 2a89cc07d..56d8cfad0 100644 --- a/app/src/main/java/org/isoron/uhabits/MainController.java +++ b/app/src/main/java/org/isoron/uhabits/MainController.java @@ -41,15 +41,15 @@ public class MainController implements ImportDataTask.Listener, ExportCSVTask.Li void refresh(Long refreshKey); + void sendFile(String filename); + + void sendEmail(String to, String subject, String content); + ProgressBar getProgressBar(); } public interface System { - void sendFile(String filename); - - void sendEmail(String to, String subject, String content); - void scheduleReminders(); void updateWidgets(); @@ -133,7 +133,7 @@ public class MainController implements ImportDataTask.Listener, ExportCSVTask.Li @Override public void onExportCSVFinished(String filename) { - if(filename != null) sys.sendFile(filename); + if(filename != null) screen.sendFile(filename); else screen.showMessage(R.string.could_not_export); } @@ -147,7 +147,7 @@ public class MainController implements ImportDataTask.Listener, ExportCSVTask.Li @Override public void onExportDBFinished(String filename) { - if(filename != null) sys.sendFile(filename); + if(filename != null) screen.sendFile(filename); else screen.showMessage(R.string.could_not_export); } @@ -169,7 +169,7 @@ public class MainController implements ImportDataTask.Listener, ExportCSVTask.Li log += "---------- BUG REPORT ENDS ------------\n"; String to = "dev@loophabits.org"; String subject = "Bug Report - Loop Habit Tracker"; - sys.sendEmail(log, to, subject); + screen.sendEmail(log, to, subject); } catch (IOException e) { diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java index e7ed2471b..634c84183 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java @@ -21,10 +21,13 @@ package org.isoron.uhabits.ui; import android.app.backup.BackupManager; import android.content.Context; +import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; +import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; @@ -43,6 +46,8 @@ import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.widgets.WidgetManager; +import java.io.File; + abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler, CommandRunner.Listener { @@ -178,4 +183,24 @@ abstract public class BaseActivity extends AppCompatActivity implements Thread.U } }.execute(); } + + public void sendFile(@NonNull String archiveFilename) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/zip"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); + startActivity(intent); + } + + public void sendEmail(String to, String subject, String content) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("message/rfc822"); + intent.putExtra(Intent.EXTRA_EMAIL, new String[] {to}); + intent.putExtra(Intent.EXTRA_SUBJECT, subject); + intent.putExtra(Intent.EXTRA_TEXT, content); + startActivity(intent); + } } From 3ffa079e24e7c9d6641c8e8ef2434b18ff1f67a8 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 28 May 2016 13:03:37 -0400 Subject: [PATCH 013/184] Remove code duplication --- .../ui/habits/edit/BaseDialogFragment.java | 15 ++++++++++++--- .../habits/edit/CreateHabitDialogFragment.java | 18 ++++-------------- .../habits/edit/EditHabitDialogFragment.java | 16 ++++------------ 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java index 5be97cbe8..96182853c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java @@ -56,11 +56,22 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment Bundle savedInstanceState) { View view = inflater.inflate(R.layout.edit_habit, container, false); - helper = new BaseDialogHelper(this, view); ButterKnife.bind(this, view); + + helper = new BaseDialogHelper(this, view); + getDialog().setTitle(getTitle()); + initializeHabits(); + restoreSavedInstance(savedInstanceState); + helper.populateForm(modifiedHabit); return view; } + protected abstract void initializeHabits(); + + protected abstract void saveHabit(); + + protected abstract int getTitle(); + @Override @SuppressWarnings("ConstantConditions") public void onSaveInstanceState(Bundle outState) @@ -138,8 +149,6 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment helper.populateFrequencyFields(modifiedHabit); } - protected abstract void saveHabit(); - @OnClick(R.id.buttonPickColor) void showColorPicker() { diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java index 8cd92750d..077a2c750 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java @@ -19,11 +19,6 @@ package org.isoron.uhabits.ui.habits.edit; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.CommandRunner; @@ -33,18 +28,13 @@ import org.isoron.uhabits.models.Habit; public class CreateHabitDialogFragment extends BaseDialogFragment { @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) + protected int getTitle() { - View view = super.onCreateView(inflater, container, savedInstanceState); - getDialog().setTitle(R.string.create_habit); - initializeHabits(); - restoreSavedInstance(savedInstanceState); - helper.populateForm(modifiedHabit); - return view; + return R.string.create_habit; } - private void initializeHabits() + @Override + protected void initializeHabits() { modifiedHabit = new Habit(); modifiedHabit.freqNum = 1; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java index 413a298cb..7ffff0aac 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java @@ -20,9 +20,6 @@ package org.isoron.uhabits.ui.habits.edit; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; @@ -42,18 +39,13 @@ public class EditHabitDialogFragment extends BaseDialogFragment } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) + protected int getTitle() { - View view = super.onCreateView(inflater, container, savedInstanceState); - getDialog().setTitle(R.string.edit_habit); - initializeHabits(); - restoreSavedInstance(savedInstanceState); - helper.populateForm(modifiedHabit); - return view; + return R.string.edit_habit; } - private void initializeHabits() + @Override + protected void initializeHabits() { Long habitId = (Long) getArguments().get("habitId"); if(habitId == null) throw new IllegalArgumentException("habitId must be specified"); From 7e8a2a0c1c44b8b8b8e6419095dce25eeed41c73 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 29 May 2016 11:40:42 -0400 Subject: [PATCH 014/184] Major refactoring of ListHabitsActivity --- app/build.gradle | 17 +- .../render_explicit_check.png | Bin 0 -> 551 bytes .../render_implicit_check.png | Bin 0 -> 505 bytes .../CheckmarkButtonView/render_unchecked.png | Bin 0 -> 559 bytes .../habits/list/CheckmarkPanelView/render.png | Bin 0 -> 1407 bytes .../isoron/uhabits/AndroidTestComponent.java | 32 ++ .../{BaseTest.java => BaseAndroidTest.java} | 15 +- .../isoron/uhabits/ui/HabitViewActions.java | 2 +- .../java/org/isoron/uhabits/ui/MainTest.java | 2 +- .../uhabits/unit/HabitsApplicationTest.java | 8 +- .../commands/ArchiveHabitsCommandTest.java | 8 +- .../commands/ChangeHabitColorCommandTest.java | 8 +- .../unit/commands/CreateHabitCommandTest.java | 8 +- .../commands/DeleteHabitsCommandTest.java | 8 +- .../unit/commands/EditHabitCommandTest.java | 8 +- .../commands/ToggleRepetitionCommandTest.java | 8 +- .../commands/UnarchiveHabitsCommandTest.java | 8 +- .../unit/io/HabitsCSVExporterTest.java | 8 +- .../isoron/uhabits/unit/io/ImportTest.java | 8 +- .../unit/models/CheckmarkListTest.java | 8 +- .../isoron/uhabits/unit/models/HabitTest.java | 8 +- .../unit/models/RepetitionListTest.java | 8 +- .../uhabits/unit/models/ScoreListTest.java | 8 +- .../isoron/uhabits/unit/models/ScoreTest.java | 8 +- .../uhabits/unit/tasks/ExportCSVTaskTest.java | 8 +- .../uhabits/unit/tasks/ExportDBTaskTest.java | 8 +- .../unit/tasks/ImportDataTaskTest.java | 8 +- .../list/view/CheckmarkButtonViewTest.java | 187 ++++++++++ .../list/view/CheckmarkPanelViewTest.java | 97 +++++ .../unit/views/CheckmarkWidgetViewTest.java | 4 +- .../unit/views/HabitFrequencyViewTest.java | 4 +- .../unit/views/HabitHistoryViewTest.java | 4 +- .../unit/views/HabitScoreViewTest.java | 4 +- .../unit/views/HabitStreakViewTest.java | 4 +- .../uhabits/unit/views/NumberViewTest.java | 4 +- .../uhabits/unit/views/RingViewTest.java | 4 +- .../isoron/uhabits/unit/views/ViewTest.java | 22 +- .../org/isoron/uhabits/AndroidComponent.java | 31 ++ .../org/isoron/uhabits/AndroidModule.java | 54 +++ .../org/isoron/uhabits/BaseComponent.java | 53 +++ .../uhabits/HabitBroadcastReceiver.java | 26 +- .../org/isoron/uhabits/HabitsApplication.java | 131 +------ .../org/isoron/uhabits/HabitsBackupAgent.java | 6 +- .../java/org/isoron/uhabits/MainActivity.java | 223 +----------- .../org/isoron/uhabits/MainController.java | 180 ---------- .../uhabits/commands/CommandRunner.java | 32 +- .../uhabits/models/ModelObservable.java | 2 +- .../uhabits/tasks/ToggleRepetitionTask.java | 11 +- .../org/isoron/uhabits/ui/BaseActivity.java | 208 ++++------- .../java/org/isoron/uhabits/ui/BaseMenu.java | 60 ++++ .../org/isoron/uhabits/ui/BaseRootView.java | 110 ++++++ .../org/isoron/uhabits/ui/BaseScreen.java | 217 ++++++++++++ .../isoron/uhabits/ui/BaseSelectionMenu.java | 87 +++++ .../org/isoron/uhabits/ui/BaseSystem.java | 166 +++++++++ .../org/isoron/uhabits/ui/HintManager.java | 81 ----- ...ogressBar.java => ProgressBarWrapper.java} | 4 +- .../uhabits/ui/about/AboutActivity.java | 53 +-- .../isoron/uhabits/ui/about/package-info.java | 23 ++ .../ui/habits/edit/BaseDialogFragment.java | 146 ++++---- .../ui/habits/edit/BaseDialogHelper.java | 126 ++++--- .../edit/CreateHabitDialogFragment.java | 3 +- .../habits/edit/EditHabitDialogFragment.java | 6 +- .../ui/habits/edit/HistoryEditorDialog.java | 26 +- .../ui/habits/edit/WeekdayPickerDialog.java | 19 +- .../ui/habits/list/HabitListAdapter.java | 99 ------ .../ui/habits/list/HabitListLoader.java | 223 ------------ .../list/HabitListSelectionCallback.java | 229 ------------ .../uhabits/ui/habits/list/HabitListView.java | 273 -------------- .../ui/habits/list/ListHabitsActivity.java | 46 +++ .../ui/habits/list/ListHabitsController.java | 152 +++++++- .../ui/habits/list/ListHabitsFragment.java | 236 ------------- .../ui/habits/list/ListHabitsHelper.java | 307 ---------------- .../ui/habits/list/ListHabitsMenu.java | 96 +++++ .../ui/habits/list/ListHabitsRootView.java | 173 +++++++++ .../ui/habits/list/ListHabitsScreen.java | 236 +++++++++++++ .../habits/list/ListHabitsSelectionMenu.java | 200 +++++++++++ .../CheckmarkButtonController.java | 98 +++++ .../list/controllers/HabitCardController.java | 61 ++++ .../controllers/HabitCardListController.java | 317 +++++++++++++++++ .../habits/list/controllers/package-info.java | 23 ++ .../list/model/HabitCardListAdapter.java | 222 ++++++++++++ .../habits/list/model/HabitCardListCache.java | 334 ++++++++++++++++++ .../ui/habits/list/model/HintList.java | 81 +++++ .../ui/habits/list/model/package-info.java | 23 ++ .../uhabits/ui/habits/list/package-info.java | 23 ++ .../list/views/CheckmarkButtonView.java | 116 ++++++ .../habits/list/views/CheckmarkPanelView.java | 190 ++++++++++ .../habits/list/views/HabitCardListView.java | 165 +++++++++ .../ui/habits/list/views/HabitCardView.java | 216 +++++++++++ .../ui/habits/list/views/HeaderView.java | 94 +++++ .../ui/habits/list/views/HintView.java | 138 ++++++++ .../ui/habits/show/ShowHabitActivity.java | 13 +- .../ui/habits/show/ShowHabitFragment.java | 69 ++-- .../ui/habits/show/ShowHabitHelper.java | 67 ++-- .../uhabits/ui/intro/IntroActivity.java | 16 +- .../isoron/uhabits/ui/intro/package-info.java | 23 ++ .../uhabits/ui/settings/FilePickerDialog.java | 23 +- .../uhabits/ui/settings/SettingsActivity.java | 12 +- .../uhabits/ui/settings/SettingsFragment.java | 54 +-- .../uhabits/ui/settings/package-info.java | 23 ++ .../org/isoron/uhabits/utils/Preferences.java | 75 +++- ...st_habits_fragment.xml => list_habits.xml} | 83 ++--- .../main/res/layout/list_habits_activity.xml | 46 --- ...t_habits_item.xml => list_habits_card.xml} | 35 +- ...eck.xml => list_habits_card_checkmark.xml} | 0 ...k.xml => list_habits_header_checkmark.xml} | 0 app/src/main/res/layout/list_habits_hint.xml | 46 +++ .../main/res/layout/list_habits_preview.xml | 123 +++++++ .../main/res/menu/list_habits_fragment.xml | 39 -- app/src/main/res/menu/main_activity.xml | 14 + .../res/values-v21/styles_list_habits.xml | 4 +- app/src/main/res/values/styles.xml | 2 - .../main/res/values/styles_list_habits.xml | 3 - .../java/org/isoron/uhabits/BaseUnitTest.java | 45 +++ .../org/isoron/uhabits/TestComponent.java | 32 ++ .../java/org/isoron/uhabits/TestModule.java | 56 +++ .../list/model/HabitCardListAdapterTest.java | 112 ++++++ .../views/CheckmarkButtonControllerTest.java | 95 +++++ .../list/views/HabitCardControllerTest.java | 74 ++++ .../views/HabitCardListControllerTest.java | 178 ++++++++++ .../ui/habits/list/views/package-info.java | 23 ++ build.gradle | 3 + gradle.properties | 2 +- 123 files changed, 5727 insertions(+), 2664 deletions(-) create mode 100644 app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_explicit_check.png create mode 100644 app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_implicit_check.png create mode 100644 app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_unchecked.png create mode 100644 app/src/androidTest/assets/views/ui/habits/list/CheckmarkPanelView/render.png create mode 100644 app/src/androidTest/java/org/isoron/uhabits/AndroidTestComponent.java rename app/src/androidTest/java/org/isoron/uhabits/{BaseTest.java => BaseAndroidTest.java} (83%) create mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java create mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java create mode 100644 app/src/main/java/org/isoron/uhabits/AndroidComponent.java create mode 100644 app/src/main/java/org/isoron/uhabits/AndroidModule.java create mode 100644 app/src/main/java/org/isoron/uhabits/BaseComponent.java delete mode 100644 app/src/main/java/org/isoron/uhabits/MainController.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/HintManager.java rename app/src/main/java/org/isoron/uhabits/ui/{AndroidProgressBar.java => ProgressBarWrapper.java} (91%) create mode 100644 app/src/main/java/org/isoron/uhabits/ui/about/package-info.java delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsMenu.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/CheckmarkButtonController.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HintList.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HintView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java rename app/src/main/res/layout/{list_habits_fragment.xml => list_habits.xml} (56%) delete mode 100644 app/src/main/res/layout/list_habits_activity.xml rename app/src/main/res/layout/{list_habits_item.xml => list_habits_card.xml} (63%) rename app/src/main/res/layout/{list_habits_item_check.xml => list_habits_card_checkmark.xml} (100%) rename app/src/main/res/layout/{list_habits_header_check.xml => list_habits_header_checkmark.xml} (100%) create mode 100644 app/src/main/res/layout/list_habits_hint.xml create mode 100644 app/src/main/res/layout/list_habits_preview.xml delete mode 100644 app/src/main/res/menu/list_habits_fragment.xml create mode 100644 app/src/test/java/org/isoron/uhabits/BaseUnitTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/TestComponent.java create mode 100644 app/src/test/java/org/isoron/uhabits/TestModule.java create mode 100644 app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapterTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonControllerTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListControllerTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/ui/habits/list/views/package-info.java diff --git a/app/build.gradle b/app/build.gradle index 8164b62ab..08067b99c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' +apply plugin: 'com.getkeepsafe.dexcount' +apply plugin: 'me.tatarka.retrolambda' android { compileSdkVersion 23 @@ -14,7 +16,6 @@ android { buildConfigField "String", "databaseFilename", "\"uhabits.db\"" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - //testInstrumentationRunnerArgument "size", "small" } buildTypes { @@ -30,6 +31,10 @@ android { lintOptions { checkReleaseBuilds false } + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } } dependencies { @@ -41,11 +46,21 @@ dependencies { compile 'org.apmem.tools:layouts:1.10@aar' compile 'com.opencsv:opencsv:3.7' compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT' + compile 'com.jakewharton:butterknife:8.0.1' apt 'com.jakewharton:butterknife-compiler:8.0.1' + compile 'com.google.dagger:dagger:2.2' + apt 'com.google.dagger:dagger-compiler:2.2' + testApt 'com.google.dagger:dagger-compiler:2.2' + androidTestApt 'com.google.dagger:dagger-compiler:2.2' + provided 'javax.annotation:jsr250-api:1.0' + compile project(':libs:drag-sort-listview:library') + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:1.10.19' + androidTestCompile 'com.android.support:support-annotations:23.3.0' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' diff --git a/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_explicit_check.png b/app/src/androidTest/assets/views/ui/habits/list/CheckmarkButtonView/render_explicit_check.png new file mode 100644 index 0000000000000000000000000000000000000000..1f53b9ae5523ae670b07036b24f0c951af0bfbb5 GIT binary patch literal 551 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)EX7WqAsj$Z!;#X#z`%IV)5S5Q zV$Rze8~ue7C60f5AH0Bda?%t1kMatJiH>c2PLCS$R9+nD%8b0nnkmlibHr6WM&;s> zg+4wxK7CeJGxOBcVuBZ}XYO2bX`bn{@3pi4*xkOn_pI&DXZK>(DJWrpNiyBG2kJ9( z);87m+>|!k&Y#p{u#fTe)#VWnuRd-3UJ|+Z*Pq-MhH6gt3skBeG0sQ~iJ0;K*u{^} zCoJx{FMQ)h71Nuqd!N0$FmG3L@$!YoG+TCE^muZV*)GSJ?{mF=fAO}@ zPt}f{6}Efu&aHFJAGtN3e$V;dzNMT0mz$4;+>slHA}jZ+%Qf7rU1lv`khkl3&7+SB zUxXv-%cN)jnYek8r`gt?;u!QEZ;Wh6};B-N_f=73YV9uJB3YJ7O zMQ<+dX0yhYp4LDy&!j6Gj1PT&{f+tC`NL=Qv>AccAV5Q<*Q6&3o|D?sHt&3%&Gz{9 z!TQv-w#LWa^zqGIGP!DR-Hh)tt$XAx_~RdWzUNN7A5}f)yUzbS*1pc^rX7n`#H|du zwCCB+aP1GXD~it99xwbe>A6oF^RmocHm~$K>U|z&)AqxAGPS+*lR1+Pql)oAge0nWMWWZTVGr&hmQ4 z@7|=i7)6FOGVD5;ZRQILYvVq0&zdMrD%>j?Qpi zq3a8cIKnsGk@_w2>%Z?azZ3fW<;$l}23mjwBxFCwHmE$UtP2gjx^>CZ5-Z*Bw!7oj zJ9~C3aKt^bsI&EN{k!jnp2F!(k+IiT&b;d#CF)vu^Lvi*cB9R|cdeVhNoV%l^_TME z!V{*Y8b80|?-{>6`P|Ou20YILMfPr;uiN!#lg?>&XT2F$KSU6@NxO15THwD{uP|3GV`WysQ@Aiy(-a?*- zp2{2nzkUlG7n)}>iDBtsXPMxNOB_-!&*zC~PtdJlu~Twi&}->byn55C>?KR@GkUU{ zUb1h?EA%`1+&#rmBu=93Gte8i6?U7vkC?OM5OW2$0Y{U#r+jSj+qIIe7jHXs$e-Q4 z=Yv>+%<=VUW$Kxi<=$Ie|JJg@aI^J%@zrs^52$#`oqZE_)~n>)(pve}u#QaQFYkWq o1xPtcE7!i$u0@Ik28I`3xKox2GI>VjdoTak1^@u2 z@X(-W0Knk5K28D}>Mioa!GH8_>jfA*7N|!NFr^#-*am?I1;iFG)iQC0ZLamuqL?a! zyst+jF+GH@c$uOJ;hN7;t0LHTwj_3j6)j9$8oa|M>H z&_=%+*q5DST)stv48l5`89KtVyD|>1gEx$s@u6zxx;&_|D@H7HpSRVup?W3X4N^ga z8yoB#(dW>uO<8TLtYcO5*rEj@kp^rm11*Gx=|PN%bsnzST>gv5<4_@Whu_{m9EcV-NHPdZ-3OD9)M3mH&!wy3tM^MzNw z6R-qIAQI;#-jF)6c875pTA&JsYXzB{{{BshD2^G{jmqK+Dyz`m@FpWeLx1uE?#boM z>GyW*J2mlgCW3%&Jo`2znrif1?&X9{OJ!Wp;4HQVW(0QHvUUkGFHR^5J%|ho*-cNm zzWf5(%*?F#CwNr0Lf$(BJ?0WY+K+vp@NWv<*` zUwX^qv)$E5s^2`y-$fq8!rkFDvl@;64hc1Pw$`@M*vgGk!e)KVoZwiH``G_i^)_NXO-`LNg$qhQgI$LLhAc6=fzyJJ~g7n#`HX zUmt_%8)80}*&S`gaUHAsxJipHsU82`Rv*T4BfnkVFyPRCOrBKr)`CR=gTcDi+Y7X~ zwtd>z^De%O>m!8}h0Vv$SJP5vjrN70M)_R>&oL&h!cO=wZc-WLK^j*us+>sn{Eqfg5f7cgZdJW{`kHMIRhX)J{?Ym#*qSC^|#|Kgmy3?f}Z2%!_pm^JoB?5(3O%ne&wa#9~( zwV5@C5GuyvMquSXvxK1|z3Yoe4n?fH(*y$jHC5I#^n_i_vd-GU3b6RmvuChn12n*h zP8O_u`Xoa(jom}mZ1=jhkQrB^kWm11K4Ui(v$nT30!_vDOCBp@mYp@0eduEM=43s4 z8{%2-x+ObW&3)Qj?f)$0$fQtJ&iSHLKa_X|!tm3A$^}R6gi + * + * 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; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = {AndroidModule.class}) +public interface AndroidTestComponent extends BaseComponent +{ + void inject(BaseAndroidTest baseAndroidTest); +} + diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java similarity index 83% rename from app/src/androidTest/java/org/isoron/uhabits/BaseTest.java rename to app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java index 2cf353d2f..8a5d6255a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java @@ -27,11 +27,14 @@ import android.support.test.InstrumentationRegistry; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.utils.Preferences; import org.junit.Before; import java.util.concurrent.TimeoutException; -public class BaseTest +import javax.inject.Inject; + +public class BaseAndroidTest { protected Context testContext; protected Context targetContext; @@ -39,8 +42,12 @@ public class BaseTest public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC) + @Inject + protected Preferences prefs; + protected AndroidTestComponent androidTestComponent; + @Before - public void setup() + public void setUp() { if(!isLooperPrepared) { @@ -53,6 +60,10 @@ public class BaseTest InterfaceUtils.setFixedTheme(R.style.AppBaseTheme); DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME); + + androidTestComponent = DaggerAndroidTestComponent.builder().build(); + HabitsApplication.setComponent(androidTestComponent); + androidTestComponent.inject(this); } protected void waitForAsyncTasks() throws InterruptedException, TimeoutException diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java index afa630ea0..cf37b0708 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java @@ -61,7 +61,7 @@ public class HabitViewActions @Override public void perform(UiController uiController, View view) { - if (view.getId() != R.id.llButtons) + if (view.getId() != R.id.checkmarkPanel) throw new InvalidParameterException("View must have id llButtons"); LinearLayout llButtons = (LinearLayout) view; diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index fe6d71d28..82732735d 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -191,7 +191,7 @@ public class MainTest String name = addHabit(true); onData(allOf(is(instanceOf(Habit.class)), withName(name))) - .onChildView(withId(R.id.llButtons)) + .onChildView(withId(R.id.checkmarkPanel)) .perform(toggleAllCheckmarks()); Thread.sleep(1200); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java index 72e721d37..317b8248c 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java @@ -23,8 +23,9 @@ import android.os.Build; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.ui.BaseSystem; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,7 +36,7 @@ import static org.hamcrest.Matchers.containsString; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitsApplicationTest extends BaseTest +public class HabitsApplicationTest extends BaseAndroidTest { @Test public void test_getLogcat() throws IOException @@ -49,7 +50,8 @@ public class HabitsApplicationTest extends BaseTest HabitsApplication app = HabitsApplication.getInstance(); assert(app != null); - String log = app.getLogcat(); + BaseSystem system = new BaseSystem(targetContext); + String log = system.getLogcat(); assertThat(log, containsString(msg)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java index 24be62f7e..d00881506 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ArchiveHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -37,16 +37,16 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @SmallTest -public class ArchiveHabitsCommandTest extends BaseTest +public class ArchiveHabitsCommandTest extends BaseAndroidTest { private ArchiveHabitsCommand command; private Habit habit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habit = HabitFixtures.createShortHabit(); command = new ArchiveHabitsCommand(Collections.singletonList(habit)); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java index cc6c886d1..dd9f334f4 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ChangeHabitColorCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -37,15 +37,15 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class ChangeHabitColorCommandTest extends BaseTest +public class ChangeHabitColorCommandTest extends BaseAndroidTest { private ChangeHabitColorCommand command; private LinkedList habits; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habits = new LinkedList<>(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java index d97fa2e07..60ee83b9a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -38,16 +38,16 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class CreateHabitCommandTest extends BaseTest +public class CreateHabitCommandTest extends BaseAndroidTest { private CreateHabitCommand command; private Habit model; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); model = new Habit(); model.name = "New habit"; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java index 36a97198a..045529c04 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -39,7 +39,7 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class DeleteHabitsCommandTest extends BaseTest +public class DeleteHabitsCommandTest extends BaseAndroidTest { private DeleteHabitsCommand command; private LinkedList habits; @@ -48,9 +48,9 @@ public class DeleteHabitsCommandTest extends BaseTest public ExpectedException thrown = ExpectedException.none(); @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habits = new LinkedList<>(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java index 94d765870..a3c04fd82 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.EditHabitCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -37,7 +37,7 @@ import static org.hamcrest.Matchers.greaterThan; @RunWith(AndroidJUnit4.class) @SmallTest -public class EditHabitCommandTest extends BaseTest +public class EditHabitCommandTest extends BaseAndroidTest { private EditHabitCommand command; @@ -46,9 +46,9 @@ public class EditHabitCommandTest extends BaseTest private Long id; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habit = HabitFixtures.createShortHabit(); habit.name = "original"; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java index 95993fbaa..fb22aea6b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; @@ -36,7 +36,7 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @SmallTest -public class ToggleRepetitionCommandTest extends BaseTest +public class ToggleRepetitionCommandTest extends BaseAndroidTest { private ToggleRepetitionCommand command; @@ -44,9 +44,9 @@ public class ToggleRepetitionCommandTest extends BaseTest private long today; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habit = HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java index 066ff8bc4..336ee5752 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.commands; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -37,16 +37,16 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @SmallTest -public class UnarchiveHabitsCommandTest extends BaseTest +public class UnarchiveHabitsCommandTest extends BaseAndroidTest { private UnarchiveHabitsCommand command; private Habit habit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); habit = HabitFixtures.createShortHabit(); Habit.archive(Collections.singletonList(habit)); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java index 395952075..b68ec0a71 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java @@ -24,7 +24,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; @@ -45,14 +45,14 @@ import static junit.framework.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitsCSVExporterTest extends BaseTest +public class HabitsCSVExporterTest extends BaseAndroidTest { private File baseDir; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java index 2e1301bd2..6ca6ba60a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java @@ -24,7 +24,7 @@ import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.io.GenericImporter; @@ -49,15 +49,15 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @SmallTest -public class ImportTest extends BaseTest +public class ImportTest extends BaseAndroidTest { private File baseDir; private Context context; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); DateUtils.setFixedLocalTime(null); HabitFixtures.purgeHabits(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java index 98b05f0d7..9af522aef 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.models; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -42,15 +42,15 @@ import static org.isoron.uhabits.models.Checkmark.UNCHECKED; @RunWith(AndroidJUnit4.class) @SmallTest -public class CheckmarkListTest extends BaseTest +public class CheckmarkListTest extends BaseAndroidTest { Habit nonDailyHabit; private Habit emptyHabit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); nonDailyHabit = HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java index 518b64004..007ccd060 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java @@ -23,7 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.hamcrest.MatcherAssert; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -45,12 +45,12 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitTest extends BaseTest +public class HabitTest extends BaseAndroidTest { @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java index 8acf02ab7..5cfbf95e5 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.models; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Repetition; @@ -44,15 +44,15 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class RepetitionListTest extends BaseTest +public class RepetitionListTest extends BaseAndroidTest { private Habit habit; private Habit emptyHabit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habit = HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java index 017dacaec..7ab145a37 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.models; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.models.Habit; @@ -41,14 +41,14 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class ScoreListTest extends BaseTest +public class ScoreListTest extends BaseAndroidTest { private Habit habit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habit = HabitFixtures.createEmptyHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java index e6881b1b4..16f6a5297 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.models; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Score; import org.junit.Before; @@ -34,12 +34,12 @@ import static org.junit.Assert.assertThat; @RunWith(AndroidJUnit4.class) @SmallTest -public class ScoreTest extends BaseTest +public class ScoreTest extends BaseAndroidTest { @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java index 384d94b94..189dfdc96 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.tasks; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.ExportCSVTask; import org.isoron.uhabits.unit.HabitFixtures; @@ -41,12 +41,12 @@ import static org.hamcrest.core.IsNot.not; @RunWith(AndroidJUnit4.class) @SmallTest -public class ExportCSVTaskTest extends BaseTest +public class ExportCSVTaskTest extends BaseAndroidTest { @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java index 5dca0ec33..e811cabbb 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.unit.tasks; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.tasks.ExportDBTask; import org.junit.Before; import org.junit.Test; @@ -38,12 +38,12 @@ import static org.hamcrest.core.IsNot.not; @RunWith(AndroidJUnit4.class) @SmallTest -public class ExportDBTaskTest extends BaseTest +public class ExportDBTaskTest extends BaseAndroidTest { @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java index 8e67cc883..26c3acd39 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java @@ -23,7 +23,7 @@ import android.support.annotation.NonNull; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.tasks.ImportDataTask; import org.isoron.uhabits.utils.FileUtils; import org.junit.Before; @@ -40,14 +40,14 @@ import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) @SmallTest -public class ImportDataTaskTest extends BaseTest +public class ImportDataTaskTest extends BaseAndroidTest { private File baseDir; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); baseDir = FileUtils.getFilesDir("Backups"); if(baseDir == null) fail("baseDir should not be null"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java new file mode 100644 index 000000000..599a8e5f2 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java @@ -0,0 +1,187 @@ +/* + * 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.unit.ui.habits.list.view; + +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.ui.habits.list.views.CheckmarkButtonView; +import org.isoron.uhabits.unit.views.ViewTest; +import org.isoron.uhabits.utils.ColorUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class CheckmarkButtonViewTest extends ViewTest +{ + public static final String PATH = "ui/habits/list/CheckmarkButtonView/"; + + private CountDownLatch latch; + private CheckmarkButtonView view; + + @Before + public void setUp() + { + super.setUp(); + setSimilarityCutoff(0.03f); + + latch = new CountDownLatch(1); + view = new CheckmarkButtonView(targetContext); + view.setValue(Checkmark.UNCHECKED); + view.setColor(ColorUtils.CSV_PALETTE[7]); + + measureView(dpToPixels(40), dpToPixels(40), view); + } + + protected void assertRendersCheckedExplicitly() throws IOException + { + assertRenders(view, PATH + "render_explicit_check.png"); + } + + protected void assertRendersUnchecked() throws IOException + { + assertRenders(view, PATH + "render_unchecked.png"); + } + + protected void assertRendersCheckedImplicitly() throws IOException + { + assertRenders(view, PATH + "render_implicit_check.png"); + } + + @Test + public void testRender_unchecked() throws Exception + { + view.setValue(Checkmark.UNCHECKED); + assertRendersUnchecked(); + } + + @Test + public void testRender_explicitCheck() throws Exception + { + view.setValue(Checkmark.CHECKED_EXPLICITLY); + assertRendersCheckedExplicitly(); + } + + @Test + public void testRender_implicitCheck() throws Exception + { + view.setValue(Checkmark.CHECKED_IMPLICITLY); + assertRendersCheckedImplicitly(); + } + +// @Test +// public void testLongClick() throws Exception +// { +// setOnToggleListener(); +// view.performLongClick(); +// waitForLatch(); +// assertRendersCheckedExplicitly(); +// } +// +// @Test +// public void testClick_withShortToggle_fromUnchecked() throws Exception +// { +// Preferences.getInstance().setShortToggleEnabled(true); +// view.setValue(Checkmark.UNCHECKED); +// setOnToggleListenerAndPerformClick(); +// assertRendersCheckedExplicitly(); +// } +// +// @Test +// public void testClick_withShortToggle_fromChecked() throws Exception +// { +// Preferences.getInstance().setShortToggleEnabled(true); +// view.setValue(Checkmark.CHECKED_EXPLICITLY); +// setOnToggleListenerAndPerformClick(); +// assertRendersUnchecked(); +// } +// +// @Test +// public void testClick_withShortToggle_withoutListener() throws Exception +// { +// Preferences.getInstance().setShortToggleEnabled(true); +// view.setValue(Checkmark.CHECKED_EXPLICITLY); +// view.setController(null); +// view.performClick(); +// assertRendersUnchecked(); +// } +// +// protected void setOnToggleListenerAndPerformClick() throws InterruptedException +// { +// setOnToggleListener(); +// view.performClick(); +// waitForLatch(); +// } +// +// @Test +// public void testClick_withoutShortToggle() throws Exception +// { +// Preferences.getInstance().setShortToggleEnabled(false); +// setOnInvalidToggleListener(); +// view.performClick(); +// waitForLatch(); +// assertRendersUnchecked(); +// } + +// protected void setOnInvalidToggleListener() +// { +// view.setController(new CheckmarkButtonView.Controller() +// { +// @Override +// public void onToggleCheckmark(CheckmarkButtonView view, long timestamp) +// { +// fail(); +// } +// +// @Override +// public void onInvalidToggle(CheckmarkButtonView v) +// { +// assertThat(v, equalTo(view)); +// latch.countDown(); +// } +// }); +// } + +// protected void setOnToggleListener() +// { +// view.setController(new CheckmarkButtonView.Controller() +// { +// @Override +// public void onToggleCheckmark(CheckmarkButtonView v, long t) +// { +// assertThat(v, equalTo(view)); +// assertThat(t, equalTo(DateUtils.getStartOfToday())); +// latch.countDown(); +// } +// +// @Override +// public void onInvalidToggle(CheckmarkButtonView view) +// { +// fail(); +// } +// }); +// } +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java new file mode 100644 index 000000000..1f6edd1c4 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java @@ -0,0 +1,97 @@ +/* + * 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.unit.ui.habits.list.view; + +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView; +import org.isoron.uhabits.unit.views.ViewTest; +import org.isoron.uhabits.utils.ColorUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class CheckmarkPanelViewTest extends ViewTest +{ + public static final String PATH = "ui/habits/list/CheckmarkPanelView/"; + + private CountDownLatch latch; + private CheckmarkPanelView view; + private int checkmarks[]; + + @Override + @Before + public void setUp() + { + super.setUp(); + setSimilarityCutoff(0.03f); + prefs.setShouldReverseCheckmarks(false); + + Habit habit = new Habit(); + + latch = new CountDownLatch(1); + checkmarks = new int[]{Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED, + Checkmark.CHECKED_IMPLICITLY, Checkmark.CHECKED_EXPLICITLY}; + + view = new CheckmarkPanelView(targetContext); + view.setHabit(habit); + view.setCheckmarkValues(checkmarks); + view.setColor(ColorUtils.CSV_PALETTE[7]); + + measureView(dpToPixels(200), dpToPixels(200), view); + } + +// protected void waitForLatch() throws InterruptedException +// { +// assertTrue("Latch timeout", latch.await(1, TimeUnit.SECONDS)); +// } + + @Test + public void testRender() throws Exception + { + assertRenders(view, PATH + "render.png"); + } + +// @Test +// public void testToggleCheckmark_withLeftToRight() throws Exception +// { +// setToggleListener(); +// view.getButton(1).performToggle(); +// waitForLatch(); +// } +// +// @Test +// public void testToggleCheckmark_withReverseCheckmarks() throws Exception +// { +// prefs.setShouldReverseCheckmarks(true); +// view.setCheckmarkValues(checkmarks); // refresh after preference change +// +// setToggleListener(); +// view.getButton(2).performToggle(); +// waitForLatch(); +// } +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java index 45c91242a..5cde3d739 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java @@ -42,9 +42,9 @@ public class CheckmarkWidgetViewTest extends ViewTest private Habit habit; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); InterfaceUtils.setFixedTheme(R.style.TransparentWidgetTheme); habit = HabitFixtures.createShortHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java index 590e2c9d2..b99cb4c89 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java @@ -36,9 +36,9 @@ public class HabitFrequencyViewTest extends ViewTest private HabitFrequencyView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); Habit habit = HabitFixtures.createLongHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java index 9982fd18a..7bf667491 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java @@ -43,9 +43,9 @@ public class HabitHistoryViewTest extends ViewTest private HabitHistoryView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habit = HabitFixtures.createLongHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java index a7f1228b9..1f5b13a2f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java @@ -38,9 +38,9 @@ public class HabitScoreViewTest extends ViewTest private HabitScoreView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); habit = HabitFixtures.createLongHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java index ececee945..3b65c5a5d 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java @@ -36,9 +36,9 @@ public class HabitStreakViewTest extends ViewTest private HabitStreakView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); HabitFixtures.purgeHabits(); Habit habit = HabitFixtures.createLongHabit(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java index b4cdb0862..ca61aac04 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java @@ -38,9 +38,9 @@ public class NumberViewTest extends ViewTest private NumberView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); view = new NumberView(targetContext); view.setLabel("Hello world"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java index d59ee499e..972fae3c8 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java @@ -38,9 +38,9 @@ public class RingViewTest extends ViewTest private RingView view; @Before - public void setup() + public void setUp() { - super.setup(); + super.setUp(); view = new RingView(targetContext); view.setPercentage(0.6f); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index 8e9f527b3..691e9d50e 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -26,7 +26,7 @@ import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; -import org.isoron.uhabits.BaseTest; +import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.tasks.BaseTask; @@ -39,10 +39,23 @@ import java.io.InputStream; import static junit.framework.Assert.fail; -public class ViewTest extends BaseTest +public class ViewTest extends BaseAndroidTest { - protected static final double SIMILARITY_CUTOFF = 0.09; + protected static final double DEFAULT_SIMILARITY_CUTOFF = 0.09; public static final int HISTOGRAM_BIN_SIZE = 8; + private double similarityCutoff; + + @Override + public void setUp() + { + super.setUp(); + similarityCutoff = DEFAULT_SIMILARITY_CUTOFF; + } + + protected void setSimilarityCutoff(double similarityCutoff) + { + this.similarityCutoff = similarityCutoff; + } protected void measureView(int width, int height, View view) { @@ -70,7 +83,8 @@ public class ViewTest extends BaseTest double distance; boolean similarEnough = true; - if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) > SIMILARITY_CUTOFF) + if ((distance = compareHistograms(getHistogram(actual), getHistogram(scaledExpected))) > + similarityCutoff) { similarEnough = false; errorMessage.append(String.format( diff --git a/app/src/main/java/org/isoron/uhabits/AndroidComponent.java b/app/src/main/java/org/isoron/uhabits/AndroidComponent.java new file mode 100644 index 000000000..45eb998d9 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/AndroidComponent.java @@ -0,0 +1,31 @@ +/* + * 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; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = {AndroidModule.class}) +public interface AndroidComponent extends BaseComponent +{ +} + diff --git a/app/src/main/java/org/isoron/uhabits/AndroidModule.java b/app/src/main/java/org/isoron/uhabits/AndroidModule.java new file mode 100644 index 000000000..3bbc1d4d1 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/AndroidModule.java @@ -0,0 +1,54 @@ +/* + * 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; + +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class AndroidModule +{ + @Provides + @Singleton + Preferences providePreferences() + { + return new Preferences(); + } + + @Provides + @Singleton + CommandRunner provideCommandRunner() + { + return new CommandRunner(); + } + + @Provides + @Singleton + HabitCardListCache provideHabitCardListCache() + { + return new HabitCardListCache(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/BaseComponent.java b/app/src/main/java/org/isoron/uhabits/BaseComponent.java new file mode 100644 index 000000000..ea1b6cecb --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/BaseComponent.java @@ -0,0 +1,53 @@ +/* + * 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; + +import org.isoron.uhabits.tasks.ToggleRepetitionTask; +import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; +import org.isoron.uhabits.ui.habits.list.ListHabitsSelectionMenu; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; +import org.isoron.uhabits.ui.habits.list.ListHabitsController; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.isoron.uhabits.ui.habits.list.model.HintList; +import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView; + +public interface BaseComponent +{ + void inject(CheckmarkButtonController checkmarkButtonController); + + void inject(ListHabitsController listHabitsController); + + void inject(CheckmarkPanelView checkmarkPanelView); + + void inject(ToggleRepetitionTask toggleRepetitionTask); + + void inject(BaseDialogFragment baseDialogFragment); + + void inject(HabitCardListCache habitCardListCache); + + void inject(HabitBroadcastReceiver habitBroadcastReceiver); + + void inject(ListHabitsSelectionMenu listHabitsSelectionMenu); + + void inject(HintList hintList); + + void inject(HabitCardListAdapter habitCardListAdapter); +} diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java index c923b4e34..d992bda62 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java @@ -38,16 +38,18 @@ import android.support.v4.content.LocalBroadcastManager; import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.widgets.WidgetManager; import java.util.Date; +import javax.inject.Inject; + public class HabitBroadcastReceiver extends BroadcastReceiver { public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK"; @@ -55,6 +57,15 @@ public class HabitBroadcastReceiver extends BroadcastReceiver public static final String ACTION_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER"; public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE"; + @Inject + CommandRunner commandRunner; + + public HabitBroadcastReceiver() + { + super(); + HabitsApplication.getComponent().inject(this); + } + @Override public void onReceive(final Context context, Intent intent) { @@ -85,14 +96,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver private void createReminderAlarmsDelayed(final Context context) { - new Handler().postDelayed(new Runnable() - { - @Override - public void run() - { - ReminderUtils.createReminderAlarms(context); - } - }, 5000); + new Handler().postDelayed(() -> ReminderUtils.createReminderAlarms(context), 5000); } private void snoozeHabit(Context context, Intent intent) @@ -119,7 +123,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver if(habit != null) { ToggleRepetitionCommand command = new ToggleRepetitionCommand(habit, timestamp); - CommandRunner.getInstance().execute(command, habitId); + commandRunner.execute(command, habitId); } dismissNotification(context, habitId); diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index 9186d5e25..19706124c 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -21,28 +21,15 @@ package org.isoron.uhabits; import android.app.Application; import android.content.Context; -import android.os.Environment; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.view.WindowManager; import com.activeandroid.ActiveAndroid; -import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.DatabaseUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.FileUtils; -import org.isoron.uhabits.utils.ReminderUtils; -import org.isoron.uhabits.widgets.WidgetManager; -import java.io.BufferedReader; import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.LinkedList; -public class HabitsApplication extends Application implements MainController.System +public class HabitsApplication extends Application { public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH"; public static final int RESULT_IMPORT_DATA = 1; @@ -55,6 +42,7 @@ public class HabitsApplication extends Application implements MainController.Sys @Nullable private static Context context; + private static BaseComponent component; public static boolean isTestMode() { @@ -82,12 +70,23 @@ public class HabitsApplication extends Application implements MainController.Sys return application; } + public static BaseComponent getComponent() + { + return component; + } + + public static void setComponent(BaseComponent component) + { + HabitsApplication.component = component; + } + @Override public void onCreate() { super.onCreate(); HabitsApplication.context = this; HabitsApplication.application = this; + component = DaggerAndroidComponent.builder().build(); if (isTestMode()) { @@ -105,108 +104,4 @@ public class HabitsApplication extends Application implements MainController.Sys ActiveAndroid.dispose(); super.onTerminate(); } - - public String getLogcat() throws IOException - { - int maxNLines = 250; - StringBuilder builder = new StringBuilder(); - - String[] command = new String[] { "logcat", "-d"}; - Process process = Runtime.getRuntime().exec(command); - - InputStreamReader in = new InputStreamReader(process.getInputStream()); - BufferedReader bufferedReader = new BufferedReader(in); - - LinkedList log = new LinkedList<>(); - - String line; - while ((line = bufferedReader.readLine()) != null) - { - log.addLast(line); - if(log.size() > maxNLines) log.removeFirst(); - } - - for(String l : log) - { - builder.append(l); - builder.append('\n'); - } - - return builder.toString(); - } - - public String getDeviceInfo() - { - if(context == null) return ""; - - StringBuilder b = new StringBuilder(); - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - - b.append(String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME)); - b.append(String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE)); - b.append(String.format("OS Version: %s (%s)\n", java.lang.System.getProperty("os.version"), - android.os.Build.VERSION.INCREMENTAL)); - b.append(String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK)); - b.append(String.format("Device: %s\n", android.os.Build.DEVICE)); - b.append(String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL, - android.os.Build.PRODUCT)); - b.append(String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER)); - b.append(String.format("Other tags: %s\n", android.os.Build.TAGS)); - b.append(String.format("Screen Width: %s\n", wm.getDefaultDisplay().getWidth())); - b.append(String.format("Screen Height: %s\n", wm.getDefaultDisplay().getHeight())); - b.append(String.format("SD Card state: %s\n\n", Environment.getExternalStorageState())); - - return b.toString(); - } - - @NonNull - public File dumpBugReportToFile() throws IOException - { - String date = DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); - - if(context == null) throw new RuntimeException("application context should not be null"); - File dir = FileUtils.getFilesDir("Logs"); - if (dir == null) throw new IOException("log dir should not be null"); - - File logFile = new File(String.format("%s/Log %s.txt", dir.getPath(), date)); - FileWriter output = new FileWriter(logFile); - output.write(getBugReport()); - output.close(); - - return logFile; - } - - @NonNull - public String getBugReport() throws IOException - { - String logcat = getLogcat(); - String deviceInfo = getDeviceInfo(); - return deviceInfo + "\n" + logcat; - } - - public void scheduleReminders() - { - new BaseTask() - { - - @Override - protected void doInBackground() - { - ReminderUtils.createReminderAlarms(getContext()); - } - }.execute(); - } - - public void updateWidgets() - { - new BaseTask() - { - - @Override - protected void doInBackground() - { - WidgetManager.updateWidgets(getContext()); - } - }.execute(); - } } diff --git a/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java b/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java index 6baa562f2..6e619ef7a 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java @@ -28,7 +28,9 @@ public class HabitsBackupAgent extends BackupAgentHelper @Override public void onCreate() { - addHelper("preferences", new SharedPreferencesBackupHelper(this, "preferences")); - addHelper("database", new FileBackupHelper(this, "../databases/uhabits.db")); + addHelper("preferences", + new SharedPreferencesBackupHelper(this, "preferences")); + addHelper("database", + new FileBackupHelper(this, "../databases/uhabits.db")); } } diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 1655c4de2..0674a8035 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -19,222 +19,13 @@ package org.isoron.uhabits; -import android.content.Intent; -import android.graphics.drawable.ColorDrawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.ActionBar; -import android.view.Menu; -import android.view.MenuItem; +import org.isoron.uhabits.ui.habits.list.ListHabitsActivity; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.ProgressBar; -import org.isoron.uhabits.ui.AndroidProgressBar; -import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.ui.about.AboutActivity; -import org.isoron.uhabits.ui.habits.list.ListHabitsFragment; -import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; -import org.isoron.uhabits.ui.intro.IntroActivity; -import org.isoron.uhabits.ui.settings.FilePickerDialog; -import org.isoron.uhabits.ui.settings.SettingsActivity; -import org.isoron.uhabits.utils.FileUtils; -import org.isoron.uhabits.utils.InterfaceUtils; - -import java.io.File; - -public class MainActivity extends BaseActivity - implements ListHabitsFragment.Listener, MainController.Screen +public class MainActivity extends ListHabitsActivity { - private MainController controller; - private ListHabitsFragment listHabitsFragment; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.list_habits_activity); - setupSupportActionBar(false); - - FragmentManager fragmentManager = getSupportFragmentManager(); - listHabitsFragment = (ListHabitsFragment) fragmentManager.findFragmentById(R.id.fragment1); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) - onPreLollipopCreate(); - - controller = new MainController(); - controller.setScreen(this); - controller.setSystem((HabitsApplication) getApplication()); - controller.onStartup(); - } - - private void onPreLollipopCreate() - { - ActionBar actionBar = getSupportActionBar(); - if(actionBar == null) return; - if(InterfaceUtils.isNightMode()) return; - - int color = getResources().getColor(R.color.grey_900); - actionBar.setBackgroundDrawable(new ColorDrawable(color)); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) - { - menu.clear(); - getMenuInflater().inflate(R.menu.main_activity, menu); - MenuItem nightModeItem = menu.findItem(R.id.action_night_mode); - nightModeItem.setChecked(InterfaceUtils.isNightMode()); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case R.id.action_night_mode: - toggleNightMode(); - return true; - - case R.id.action_settings: - showSettingsScreen(); - return true; - - case R.id.action_about: - showAboutScreen(); - return true; - - case R.id.action_faq: - showFAQScreen(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - switch (resultCode) - { - case HabitsApplication.RESULT_IMPORT_DATA: - showImportScreen(); - break; - - case HabitsApplication.RESULT_EXPORT_CSV: - controller.exportCSV(); - break; - - case HabitsApplication.RESULT_EXPORT_DB: - controller.exportDB(); - break; - - case HabitsApplication.RESULT_BUG_REPORT: - controller.sendBugReport(); - break; - } - } - - private void showFAQScreen() - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(getString(R.string.helpURL))); - startActivity(intent); - } - - private void showAboutScreen() - { - Intent intent = new Intent(this, AboutActivity.class); - startActivity(intent); - } - - private void showSettingsScreen() - { - Intent intent = new Intent(this, SettingsActivity.class); - startActivityForResult(intent, 0); - } - - private void toggleNightMode() - { - if(InterfaceUtils.isNightMode()) - InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_LIGHT); - else - InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_DARK); - - refreshTheme(); - } - - private void refreshTheme() - { - new Handler().postDelayed(new Runnable() - { - @Override - public void run() - { - Intent intent = new Intent(MainActivity.this, MainActivity.class); - - MainActivity.this.finish(); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - startActivity(intent); - - } - }, 500); // Let the menu disappear first - } - - @Override - public void onHabitClick(Habit habit) - { - showHabitScreen(habit); - } - - private void showHabitScreen(Habit habit) - { - Intent intent = new Intent(this, ShowHabitActivity.class); - intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId())); - startActivity(intent); - } - - public void showIntroScreen() - { - Intent intent = new Intent(this, IntroActivity.class); - this.startActivity(intent); - } - - public void showImportScreen() - { - File dir = FileUtils.getFilesDir(null); - if(dir == null) - { - showMessage(R.string.could_not_import); - return; - } - - FilePickerDialog picker = new FilePickerDialog(this, dir); - picker.setListener(new FilePickerDialog.OnFileSelectedListener() - { - @Override - public void onFileSelected(File file) - { - controller.importData(file); - } - }); - picker.show(); - } - - @Override - public void refresh(Long refreshKey) - { - listHabitsFragment.refresh(refreshKey); - } - - @Override - public ProgressBar getProgressBar() - { - return new AndroidProgressBar(listHabitsFragment.getProgressBar()); - } + /* + * Since changing the main activity on the manifest file causes things to + * break we always point the launcher icon to this activity instead, and + * redirect to the desired one using inheritance. + */ } diff --git a/app/src/main/java/org/isoron/uhabits/MainController.java b/app/src/main/java/org/isoron/uhabits/MainController.java deleted file mode 100644 index 56d8cfad0..000000000 --- a/app/src/main/java/org/isoron/uhabits/MainController.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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; - -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.ExportCSVTask; -import org.isoron.uhabits.tasks.ExportDBTask; -import org.isoron.uhabits.tasks.ImportDataTask; -import org.isoron.uhabits.tasks.ProgressBar; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.Preferences; - -import java.io.File; -import java.io.IOException; - -public class MainController implements ImportDataTask.Listener, ExportCSVTask.Listener, - ExportDBTask.Listener -{ - public interface Screen - { - void showIntroScreen(); - - void showMessage(Integer stringId); - - void refresh(Long refreshKey); - - void sendFile(String filename); - - void sendEmail(String to, String subject, String content); - - ProgressBar getProgressBar(); - } - - public interface System - { - void scheduleReminders(); - - void updateWidgets(); - - File dumpBugReportToFile() throws IOException; - - String getBugReport() throws IOException; - } - - System sys; - Screen screen; - Preferences prefs; - - public MainController() - { - prefs = Preferences.getInstance(); - } - - public void setScreen(Screen screen) - { - this.screen = screen; - } - - public void setSystem(System sys) - { - this.sys = sys; - } - - public void onStartup() - { - prefs.initialize(); - prefs.incrementLaunchCount(); - prefs.updateLastAppVersion(); - if(prefs.isFirstRun()) onFirstRun(); - - sys.updateWidgets(); - sys.scheduleReminders(); - } - - private void onFirstRun() - { - prefs.setFirstRun(false); - prefs.setLastHintTimestamp(DateUtils.getStartOfToday()); - screen.showIntroScreen(); - } - - public void importData(File file) - { - ImportDataTask task = new ImportDataTask(file, screen.getProgressBar()); - task.setListener(this); - task.execute(); - } - - @Override - public void onImportDataFinished(int result) - { - switch (result) - { - case ImportDataTask.SUCCESS: - screen.refresh(null); - screen.showMessage(R.string.habits_imported); - break; - - case ImportDataTask.NOT_RECOGNIZED: - screen.showMessage(R.string.file_not_recognized); - break; - - default: - screen.showMessage(R.string.could_not_import); - break; - } - } - - public void exportCSV() - { - ExportCSVTask task = new ExportCSVTask(Habit.getAll(true), screen.getProgressBar()); - task.setListener(this); - task.execute(); - } - - @Override - public void onExportCSVFinished(String filename) - { - if(filename != null) screen.sendFile(filename); - else screen.showMessage(R.string.could_not_export); - } - - public void exportDB() - { - ExportDBTask task = new ExportDBTask(screen.getProgressBar()); - task.setListener(this); - task.execute(); - } - - @Override - public void onExportDBFinished(String filename) - { - if(filename != null) screen.sendFile(filename); - else screen.showMessage(R.string.could_not_export); - } - - public void sendBugReport() - { - try - { - sys.dumpBugReportToFile(); - } - catch (IOException e) - { - // ignored - } - - try - { - String log = "---------- BUG REPORT BEGINS ----------\n"; - log += sys.getBugReport(); - log += "---------- BUG REPORT ENDS ------------\n"; - String to = "dev@loophabits.org"; - String subject = "Bug Report - Loop Habit Tracker"; - screen.sendEmail(log, to, subject); - } - catch (IOException e) - { - e.printStackTrace(); - screen.showMessage(R.string.bug_report_failed); - } - } -} diff --git a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java index ab8a55181..035e55087 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java +++ b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java @@ -19,29 +19,30 @@ package org.isoron.uhabits.commands; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + import org.isoron.uhabits.tasks.BaseTask; import java.util.LinkedList; public class CommandRunner { - public interface Listener - { - void onCommandExecuted(Command command, Long refreshKey); - } - - private static CommandRunner singleton; private LinkedList listeners; - private CommandRunner() + public CommandRunner() { listeners = new LinkedList<>(); } - public static CommandRunner getInstance() + private static CommandRunner getInstance() { - if(singleton == null) singleton = new CommandRunner(); - return singleton; + return null; + } + + public void addListener(Listener l) + { + listeners.add(l); } public void execute(final Command command, final Long refreshKey) @@ -57,7 +58,7 @@ public class CommandRunner @Override protected void onPostExecute(Void aVoid) { - for(Listener l : listeners) + for (Listener l : listeners) l.onCommandExecuted(command, refreshKey); super.onPostExecute(null); @@ -65,13 +66,14 @@ public class CommandRunner }.execute(); } - public void addListener(Listener l) + public void removeListener(Listener l) { - listeners.add(l); + listeners.remove(l); } - public void removeListener(Listener l) + public interface Listener { - listeners.remove(l); + void onCommandExecuted(@NonNull Command command, + @Nullable Long refreshKey); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java index a8f12cab7..2ff7650f3 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java +++ b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java @@ -47,7 +47,7 @@ public class ModelObservable listeners.remove(l); } - void notifyListeners() + public void notifyListeners() { for(Listener l : listeners) l.onModelChange(); } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java index 828656eb3..56c6e170b 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java @@ -19,12 +19,18 @@ package org.isoron.uhabits.tasks; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.models.Habit; +import javax.inject.Inject; + public class ToggleRepetitionTask extends BaseTask -{; +{ + @Inject + CommandRunner commandRunner; + public interface Listener { void onToggleRepetitionFinished(); } @@ -37,13 +43,14 @@ public class ToggleRepetitionTask extends BaseTask { this.timestamp = timestamp; this.habit = habit; + HabitsApplication.getComponent().inject(this); } @Override protected void doInBackground() { ToggleRepetitionCommand command = new ToggleRepetitionCommand(habit, timestamp); - CommandRunner.getInstance().execute(command, habit.getId()); + commandRunner.execute(command, habit.getId()); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java index 634c84183..bdc066fc9 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java @@ -19,188 +19,114 @@ package org.isoron.uhabits.ui; -import android.app.backup.BackupManager; -import android.content.Context; import android.content.Intent; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.ActionBar; +import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.View; -import android.widget.Toast; - -import org.isoron.uhabits.HabitBroadcastReceiver; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.widgets.WidgetManager; +import android.view.Menu; +import android.view.MenuItem; -import java.io.File; +import org.isoron.uhabits.utils.InterfaceUtils; -abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler, - CommandRunner.Listener +/** + * Base class for all activities in the application. + */ +abstract public class BaseActivity extends AppCompatActivity + implements Thread.UncaughtExceptionHandler { - private Toast toast; - - Thread.UncaughtExceptionHandler androidExceptionHandler; - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); + @Nullable + private BaseMenu baseMenu; - InterfaceUtils.applyCurrentTheme(this); + @Nullable + private Thread.UncaughtExceptionHandler androidExceptionHandler; - androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(this); - } + @Nullable + private BaseScreen screen; @Override - protected void onResume() + public boolean onCreateOptionsMenu(@Nullable Menu menu) { - super.onResume(); - CommandRunner.getInstance().addListener(this); + if (menu == null) return false; + if (baseMenu == null) return false; + return baseMenu.onCreate(getMenuInflater(), menu); } @Override - protected void onPause() + public boolean onOptionsItemSelected(@Nullable MenuItem item) { - CommandRunner.getInstance().removeListener(this); - super.onPause(); + if (item == null) return false; + if (baseMenu == null) return false; + return baseMenu.onItemSelected(item); } - public void showMessage(Integer stringId) + public void setBaseMenu(@Nullable BaseMenu baseMenu) { - if (stringId == null) return; - if (toast == null) toast = Toast.makeText(this, stringId, Toast.LENGTH_SHORT); - else toast.setText(stringId); - toast.show(); + this.baseMenu = baseMenu; } - protected void setupSupportActionBar(boolean homeButtonEnabled) + public void setScreen(@Nullable BaseScreen screen) { - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - if(toolbar == null) return; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - toolbar.setElevation(InterfaceUtils.dpToPixels(this, 2)); - - setSupportActionBar(toolbar); - - ActionBar actionBar = getSupportActionBar(); - if(actionBar == null) return; - - if(homeButtonEnabled) - actionBar.setDisplayHomeAsUpEnabled(true); + this.screen = screen; } @Override - public void uncaughtException(Thread thread, Throwable ex) + public void uncaughtException(@Nullable Thread thread, + @Nullable Throwable ex) { + if (ex == null) return; + try { ex.printStackTrace(); - ((HabitsApplication) getApplication()).dumpBugReportToFile(); - } - catch(Exception e) + new BaseSystem(this).dumpBugReportToFile(); + } catch (Exception e) { // ignored } - if(androidExceptionHandler != null) + if (androidExceptionHandler != null) androidExceptionHandler.uncaughtException(thread, ex); - else - System.exit(1); - } - - protected void setupActionBarColor(int color) - { - ActionBar actionBar = getSupportActionBar(); - if(actionBar == null) return; - - if (!InterfaceUtils.getStyledBoolean(this, R.attr.useHabitColorAsPrimary)) return; - - ColorDrawable drawable = new ColorDrawable(color); - actionBar.setBackgroundDrawable(drawable); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - { - int darkerColor = ColorUtils.mixColors(color, Color.BLACK, 0.75f); - getWindow().setStatusBarColor(darkerColor); - } + else System.exit(1); } @Override - protected void onPostResume() - { - super.onPostResume(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - hideFakeToolbarShadow(); - } - - protected void hideFakeToolbarShadow() + protected void onActivityResult(int request, int result, Intent data) { - View view = findViewById(R.id.toolbarShadow); - if(view != null) view.setVisibility(View.GONE); - - view = findViewById(R.id.headerShadow); - if(view != null) view.setVisibility(View.GONE); + if (screen == null) return; + screen.onResult(request, result, data); } - private void dismissNotifications(Context context) - { - for(Habit h : Habit.getHabitsWithReminder()) - { - if(h.checkmarks.getTodayValue() != Checkmark.UNCHECKED) - HabitBroadcastReceiver.dismissNotification(context, h); - } - } +// @Override +// public void onCommandExecuted(Command command, Long refreshKey) +// { +// window.showMessage(command.getExecuteStringId()); +// new BaseTask() +// { +// @Override +// protected void doInBackground() +// { +// dismissNotifications(BaseActivity.this); +// BackupManager.dataChanged("org.isoron.uhabits"); +// WidgetManager.updateWidgets(BaseActivity.this); +// } +// }.execute(); +// } + +// private void dismissNotifications(Context context) +// { +// for (Habit h : Habit.getHabitsWithReminder()) +// { +// if (h.checkmarks.getTodayValue() != Checkmark.UNCHECKED) +// HabitBroadcastReceiver.dismissNotification(context, h); +// } +// } @Override - public void onCommandExecuted(Command command, Long refreshKey) - { - showMessage(command.getExecuteStringId()); - new BaseTask() - { - @Override - protected void doInBackground() - { - dismissNotifications(BaseActivity.this); - BackupManager.dataChanged("org.isoron.uhabits"); - WidgetManager.updateWidgets(BaseActivity.this); - } - }.execute(); - } - - public void sendFile(@NonNull String archiveFilename) - { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); - startActivity(intent); - } - - public void sendEmail(String to, String subject, String content) + protected void onCreate(Bundle savedInstanceState) { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.setType("message/rfc822"); - intent.putExtra(Intent.EXTRA_EMAIL, new String[] {to}); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_TEXT, content); - startActivity(intent); + super.onCreate(savedInstanceState); + InterfaceUtils.applyCurrentTheme(this); + androidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java b/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java new file mode 100644 index 000000000..ffa30706a --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java @@ -0,0 +1,60 @@ +/* + * 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.ui; + +import android.support.annotation.NonNull; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +public abstract class BaseMenu +{ + private final BaseActivity activity; + + public BaseMenu(BaseActivity activity) + { + this.activity = activity; + } + + public final boolean onCreate(@NonNull MenuInflater inflater, + @NonNull Menu menu) + { + menu.clear(); + inflater.inflate(getMenuResourceId(), menu); + onCreate(menu); + return true; + } + + public void onCreate(@NonNull Menu menu) + { + } + + public boolean onItemSelected(@NonNull MenuItem item) + { + return true; + } + + protected abstract int getMenuResourceId(); + + public void invalidate() + { + activity.invalidateOptionsMenu(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java b/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java new file mode 100644 index 000000000..4016442ab --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java @@ -0,0 +1,110 @@ +/* + * 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.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.utils.InterfaceUtils; + +public abstract class BaseRootView extends FrameLayout +{ + private final Context context; + + public BaseRootView(Context context) + { + super(context); + this.context = context; + } + + public BaseRootView(Context context, AttributeSet attrs) + { + super(context, attrs); + this.context = context; + } + + public BaseRootView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + this.context = context; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public BaseRootView(Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes) + { + super(context, attrs, defStyleAttr, defStyleRes); + this.context = context; + } + + public boolean getDisplayHomeAsUp() + { + return false; + } + + @Nullable + public ProgressBar getProgressBar() + { + return null; + } + + @NonNull + public abstract Toolbar getToolbar(); + + public int getToolbarColor() + { +// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) +// { +// if (InterfaceUtils.isNightMode()) return; +// int color = activity.getResources().getColor(R.color.grey_900); +// } +// if (!InterfaceUtils.getStyledBoolean(activity, R.attr.useHabitColorAsPrimary)) return; + return Color.BLACK; + } + + private void hideFakeToolbarShadow() + { + View view = findViewById(R.id.toolbarShadow); + if (view != null) view.setVisibility(View.GONE); + + view = findViewById(R.id.headerShadow); + if (view != null) view.setVisibility(View.GONE); + } + + protected void initToolbar() + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + getToolbar().setElevation(InterfaceUtils.dpToPixels(context, 2)); + + hideFakeToolbarShadow(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java new file mode 100644 index 000000000..d61ffae53 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java @@ -0,0 +1,217 @@ +/* + * 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.ui; + +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDialogFragment; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import org.isoron.uhabits.tasks.ProgressBar; +import org.isoron.uhabits.utils.ColorUtils; + +import java.io.File; + +public abstract class BaseScreen +{ + protected BaseActivity activity; + + private Toast toast; + + @Nullable + private BaseRootView rootView; + + @Nullable + private BaseSelectionMenu selectionMenu; + + public BaseScreen(BaseActivity activity) + { + this.activity = activity; + } + + public void finishSelection() + { + if (selectionMenu == null) return; + selectionMenu.finish(); + } + + @Nullable + public ProgressBar getProgressBar() + { + if (rootView == null) return null; + return new ProgressBarWrapper(rootView.getProgressBar()); + } + + public void invalidate() + { + if (rootView == null) return; + rootView.invalidate(); + } + + public void onResult(int requestCode, int resultCode, Intent data) + { + } + + public void setMenu(@Nullable BaseMenu menu) + { + activity.setBaseMenu(menu); + } + + public void setRootView(@Nullable BaseRootView rootView) + { + this.rootView = rootView; + activity.setContentView(rootView); + if (rootView == null) return; + + initToolbar(); + } + + /** + * Set the menu to be shown when a selection is active on the screen. + * + * @param menu the menu to be shown during a selection + */ + public void setSelectionMenu(@Nullable BaseSelectionMenu menu) + { + this.selectionMenu = menu; + } + + public void showMessage(@Nullable Integer stringId) + { + if (stringId == null) return; + if (toast == null) + toast = Toast.makeText(activity, stringId, Toast.LENGTH_SHORT); + else toast.setText(stringId); + toast.show(); + } + + public void showSendEmailScreen(String to, String subject, String content) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("message/rfc822"); + intent.putExtra(Intent.EXTRA_EMAIL, new String[]{to}); + intent.putExtra(Intent.EXTRA_SUBJECT, subject); + intent.putExtra(Intent.EXTRA_TEXT, content); + activity.startActivity(intent); + } + + public void showSendFileScreen(@NonNull String archiveFilename) + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.setType("application/zip"); + intent.putExtra(Intent.EXTRA_STREAM, + Uri.fromFile(new File(archiveFilename))); + activity.startActivity(intent); + } + + /** + * Instructs the screen to start a selection. If a selection menu was + * provided, this menu will be shown instead of the regular one. + */ + public void startSelection() + { + activity.startSupportActionMode(new ActionModeWrapper()); + } + + private void initToolbar() + { + if (rootView == null) return; + + Toolbar toolbar = rootView.getToolbar(); + activity.setSupportActionBar(toolbar); + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar == null) return; + + actionBar.setDisplayHomeAsUpEnabled(rootView.getDisplayHomeAsUp()); + + int color = rootView.getToolbarColor(); + setActionBarColor(actionBar, color); + setStatusBarColor(color); + } + + private void setActionBarColor(@NonNull ActionBar actionBar, int color) + { + ColorDrawable drawable = new ColorDrawable(color); + actionBar.setBackgroundDrawable(drawable); + } + + private void setStatusBarColor(int baseColor) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + int darkerColor = + ColorUtils.mixColors(baseColor, Color.BLACK, 0.75f); + activity.getWindow().setStatusBarColor(darkerColor); + } + } + + protected void showDialog(AppCompatDialogFragment dialog, String tag) + { + dialog.show(activity.getSupportFragmentManager(), tag); + } + + private class ActionModeWrapper implements ActionMode.Callback + { + @Override + public boolean onActionItemClicked(@Nullable ActionMode mode, + @Nullable MenuItem item) + { + if (item == null || selectionMenu == null) return false; + return selectionMenu.onItemClicked(item); + } + + @Override + public boolean onCreateActionMode(@Nullable ActionMode mode, + @Nullable Menu menu) + { + if (selectionMenu == null) return false; + if (mode == null || menu == null) return false; + selectionMenu.onCreate(activity.getMenuInflater(), mode, menu); + return true; + } + + @Override + public void onDestroyActionMode(@Nullable ActionMode mode) + { + if (selectionMenu == null) return; + selectionMenu.onDestroy(); + } + + @Override + public boolean onPrepareActionMode(@Nullable ActionMode mode, + @Nullable Menu menu) + { + if (selectionMenu == null || menu == null) return false; + return selectionMenu.onPrepare(menu); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java new file mode 100644 index 000000000..960c826d2 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java @@ -0,0 +1,87 @@ +/* + * 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.ui; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +public abstract class BaseSelectionMenu +{ + @Nullable + private ActionMode actionMode; + + /** + * Finishes the selection operation. + */ + public void finish() + { + if (actionMode != null) actionMode.finish(); + } + + public void invalidate() + { + if (actionMode != null) actionMode.invalidate(); + } + + public final void onCreate(@NonNull MenuInflater menuInflater, + @NonNull ActionMode mode, + @NonNull Menu menu) + { + this.actionMode = mode; + menuInflater.inflate(getResourceId(), menu); + onCreate(menu); + } + + public void onDestroy() + { + + } + + public boolean onItemClicked(@NonNull MenuItem item) + { + return false; + } + + public boolean onPrepare(@NonNull Menu menu) + { + return false; + } + + public void setTitle(String title) + { + if (actionMode != null) actionMode.setTitle(title); + } + + protected abstract int getResourceId(); + + /** + * Called when the menu is first created, right after the menu has been + * inflated. + * + * @param menu the menu containing the buttons + */ + protected void onCreate(@NonNull Menu menu) + { + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java new file mode 100644 index 000000000..8cd9d07e4 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java @@ -0,0 +1,166 @@ +/* + * 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.ui; + +import android.content.Context; +import android.os.Environment; +import android.support.annotation.NonNull; +import android.view.WindowManager; + +import org.isoron.uhabits.BuildConfig; +import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.ReminderUtils; +import org.isoron.uhabits.widgets.WidgetManager; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.LinkedList; + +public class BaseSystem +{ + private Context context; + + public BaseSystem(Context context) + { + this.context = context; + } + + public String getLogcat() throws IOException + { + int maxNLines = 250; + StringBuilder builder = new StringBuilder(); + + String[] command = new String[]{"logcat", "-d"}; + Process process = Runtime.getRuntime().exec(command); + + InputStreamReader in = new InputStreamReader(process.getInputStream()); + BufferedReader bufferedReader = new BufferedReader(in); + + LinkedList log = new LinkedList<>(); + + String line; + while ((line = bufferedReader.readLine()) != null) + { + log.addLast(line); + if (log.size() > maxNLines) log.removeFirst(); + } + + for (String l : log) + { + builder.append(l); + builder.append('\n'); + } + + return builder.toString(); + } + + public String getDeviceInfo() + { + if (context == null) return ""; + + StringBuilder b = new StringBuilder(); + WindowManager wm = + (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + + b.append( + String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME)); + b.append( + String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE)); + b.append(String.format("OS Version: %s (%s)\n", + java.lang.System.getProperty("os.version"), + android.os.Build.VERSION.INCREMENTAL)); + b.append( + String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK)); + b.append(String.format("Device: %s\n", android.os.Build.DEVICE)); + b.append( + String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL, + android.os.Build.PRODUCT)); + b.append( + String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER)); + b.append(String.format("Other tags: %s\n", android.os.Build.TAGS)); + b.append(String.format("Screen Width: %s\n", + wm.getDefaultDisplay().getWidth())); + b.append(String.format("Screen Height: %s\n", + wm.getDefaultDisplay().getHeight())); + b.append(String.format("SD Card state: %s\n\n", + Environment.getExternalStorageState())); + + return b.toString(); + } + + @NonNull + public File dumpBugReportToFile() throws IOException + { + String date = + DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); + + if (context == null) throw new RuntimeException( + "application context should not be null"); + File dir = FileUtils.getFilesDir("Logs"); + if (dir == null) throw new IOException("log dir should not be null"); + + File logFile = + new File(String.format("%s/Log %s.txt", dir.getPath(), date)); + FileWriter output = new FileWriter(logFile); + output.write(getBugReport()); + output.close(); + + return logFile; + } + + @NonNull + public String getBugReport() throws IOException + { + String logcat = getLogcat(); + String deviceInfo = getDeviceInfo(); + return deviceInfo + "\n" + logcat; + } + + public void scheduleReminders() + { + new BaseTask() + { + + @Override + protected void doInBackground() + { + ReminderUtils.createReminderAlarms(context); + } + }.execute(); + } + + public void updateWidgets() + { + new BaseTask() + { + + @Override + protected void doInBackground() + { + WidgetManager.updateWidgets(context); + } + }.execute(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/HintManager.java b/app/src/main/java/org/isoron/uhabits/ui/HintManager.java deleted file mode 100644 index aa366833f..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/HintManager.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.ui; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.view.View; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.DateUtils; - -public class HintManager -{ - private Context context; - private SharedPreferences prefs; - private View hintView; - - public HintManager(Context context, View hintView) - { - this.context = context; - this.hintView = hintView; - prefs = PreferenceManager.getDefaultSharedPreferences(context); - } - - public void dismissHint() - { - hintView.animate().alpha(0f).setDuration(500).setListener(new AnimatorListenerAdapter() - { - @Override - public void onAnimationEnd(Animator animation) - { - hintView.setVisibility(View.GONE); - } - }); - } - - public void showHintIfAppropriate() - { - Integer lastHintNumber = prefs.getInt("last_hint_number", -1); - Long lastHintTimestamp = prefs.getLong("last_hint_timestamp", -1); - - if (DateUtils.getStartOfToday() > lastHintTimestamp) showHint(lastHintNumber + 1); - } - - private void showHint(int hintNumber) - { - String[] hints = context.getResources().getStringArray(R.array.hints); - if (hintNumber >= hints.length) return; - - prefs.edit().putInt("last_hint_number", hintNumber).apply(); - prefs.edit().putLong("last_hint_timestamp", DateUtils.getStartOfToday()).apply(); - - TextView tvContent = (TextView) hintView.findViewById(R.id.hintContent); - tvContent.setText(hints[hintNumber]); - - hintView.setAlpha(0.0f); - hintView.setVisibility(View.VISIBLE); - hintView.animate().alpha(1f).setDuration(500); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java b/app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java similarity index 91% rename from app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java rename to app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java index c366bf88c..44339ebf6 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java +++ b/app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java @@ -23,11 +23,11 @@ import android.view.View; import org.isoron.uhabits.tasks.ProgressBar; -public class AndroidProgressBar implements ProgressBar +public class ProgressBarWrapper implements ProgressBar { private final android.widget.ProgressBar progressBar; - public AndroidProgressBar(android.widget.ProgressBar progressBar) + public ProgressBarWrapper(android.widget.ProgressBar progressBar) { this.progressBar = progressBar; } diff --git a/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java b/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java index dfebc387b..e1c7600a0 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java @@ -30,32 +30,12 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.utils.InterfaceUtils; +/** + * Activity that allows the user to see information about the app itself. + * Display current version, link to Google Play and list of contributors. + */ public class AboutActivity extends BaseActivity implements View.OnClickListener { - - @Override - protected void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - setContentView(R.layout.about); - setupSupportActionBar(true); - - int color = InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); - setupActionBarColor(color); - - TextView tvVersion = (TextView) findViewById(R.id.tvVersion); - TextView tvRate = (TextView) findViewById(R.id.tvRate); - TextView tvFeedback = (TextView) findViewById(R.id.tvFeedback); - TextView tvSource = (TextView) findViewById(R.id.tvSource); - - tvVersion.setText(String.format(getResources().getString(R.string.version_n), - BuildConfig.VERSION_NAME)); - tvRate.setOnClickListener(this); - tvFeedback.setOnClickListener(this); - tvSource.setOnClickListener(this); - } - @Override public void onClick(View v) { @@ -89,4 +69,29 @@ public class AboutActivity extends BaseActivity implements View.OnClickListener } } } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.about); +// setupSupportActionBar(true); + + int color = + InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); +// setupActionBarColor(color); + + TextView tvVersion = (TextView) findViewById(R.id.tvVersion); + TextView tvRate = (TextView) findViewById(R.id.tvRate); + TextView tvFeedback = (TextView) findViewById(R.id.tvFeedback); + TextView tvSource = (TextView) findViewById(R.id.tvSource); + + tvVersion.setText( + String.format(getResources().getString(R.string.version_n), + BuildConfig.VERSION_NAME)); + tvRate.setOnClickListener(this); + tvFeedback.setOnClickListener(this); + tvSource.setOnClickListener(this); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java new file mode 100644 index 000000000..bfdb10c80 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Contains classes for AboutActivity + */ +package org.isoron.uhabits.ui.about; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java index 96182853c..7dfb86e31 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java @@ -32,7 +32,9 @@ import com.android.colorpicker.ColorPickerSwatch; import com.android.datetimepicker.time.RadialPickerLayout; import com.android.datetimepicker.time.TimePickerDialog; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; +import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; @@ -40,6 +42,8 @@ import org.isoron.uhabits.utils.Preferences; import java.util.Arrays; +import javax.inject.Inject; + import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.OnItemSelected; @@ -47,15 +51,24 @@ import butterknife.OnItemSelected; public abstract class BaseDialogFragment extends AppCompatDialogFragment { protected Habit originalHabit; + protected Habit modifiedHabit; - protected Preferences prefs = Preferences.getInstance(); + protected BaseDialogHelper helper; - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + @Inject + Preferences prefs; + + @Inject + CommandRunner commandRunner; + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.edit_habit, container, false); + HabitsApplication.getComponent().inject(this); ButterKnife.bind(this, view); helper = new BaseDialogHelper(this, view); @@ -66,11 +79,16 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment return view; } - protected abstract void initializeHabits(); - - protected abstract void saveHabit(); - - protected abstract int getTitle(); + @OnItemSelected(R.id.sFrequency) + public void onFrequencySelected(int position) + { + if (position < 0 || position > 4) throw new IllegalArgumentException(); + int freqNums[] = {1, 1, 2, 5, 3}; + int freqDens[] = {1, 7, 7, 7, 7}; + modifiedHabit.freqNum = freqNums[position]; + modifiedHabit.freqDen = freqDens[position]; + helper.populateFrequencyFields(modifiedHabit); + } @Override @SuppressWarnings("ConstantConditions") @@ -78,7 +96,7 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment { super.onSaveInstanceState(outState); outState.putInt("color", modifiedHabit.color); - if(modifiedHabit.hasReminder()) + if (modifiedHabit.hasReminder()) { outState.putInt("reminderMin", modifiedHabit.reminderMin); outState.putInt("reminderHour", modifiedHabit.reminderHour); @@ -86,15 +104,9 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment } } - protected void restoreSavedInstance(@Nullable Bundle bundle) - { - if(bundle == null) return; - modifiedHabit.color = bundle.getInt("color", modifiedHabit.color); - modifiedHabit.reminderMin = bundle.getInt("reminderMin", -1); - modifiedHabit.reminderHour = bundle.getInt("reminderHour", -1); - modifiedHabit.reminderDays = bundle.getInt("reminderDays", -1); - if(modifiedHabit.reminderMin < 0) modifiedHabit.clearReminder(); - } + protected abstract int getTitle(); + + protected abstract void initializeHabits(); @OnClick(R.id.buttonDiscard) void onButtonDiscardClick() @@ -102,15 +114,6 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment dismiss(); } - @OnClick(R.id.buttonSave) - void onSaveButtonClick() - { - helper.parseFormIntoHabit(modifiedHabit); - if (!helper.validate(modifiedHabit)) return; - saveHabit(); - dismiss(); - } - @OnClick(R.id.tvReminderTime) @SuppressWarnings("ConstantConditions") void onDateSpinnerClick() @@ -127,36 +130,49 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment showTimePicker(defaultHour, defaultMin); } + @OnClick(R.id.buttonSave) + void onSaveButtonClick() + { + helper.parseFormIntoHabit(modifiedHabit); + if (!helper.validate(modifiedHabit)) return; + saveHabit(); + dismiss(); + } + @OnClick(R.id.tvReminderDays) @SuppressWarnings("ConstantConditions") void onWeekdayClick() { - if(!modifiedHabit.hasReminder()) return; + if (!modifiedHabit.hasReminder()) return; WeekdayPickerDialog dialog = new WeekdayPickerDialog(); dialog.setListener(new OnWeekdaysPickedListener()); - dialog.setSelectedDays(DateUtils.unpackWeekdayList(modifiedHabit.reminderDays)); + dialog.setSelectedDays( + DateUtils.unpackWeekdayList(modifiedHabit.reminderDays)); dialog.show(getFragmentManager(), "weekdayPicker"); } - @OnItemSelected(R.id.sFrequency) - public void onFrequencySelected(int position) + protected void restoreSavedInstance(@Nullable Bundle bundle) { - if(position < 0 || position > 4) throw new IllegalArgumentException(); - int freqNums[] = { 1, 1, 2, 5, 3 }; - int freqDens[] = { 1, 7, 7, 7, 7 }; - modifiedHabit.freqNum = freqNums[position]; - modifiedHabit.freqDen = freqDens[position]; - helper.populateFrequencyFields(modifiedHabit); + if (bundle == null) return; + modifiedHabit.color = bundle.getInt("color", modifiedHabit.color); + modifiedHabit.reminderMin = bundle.getInt("reminderMin", -1); + modifiedHabit.reminderHour = bundle.getInt("reminderHour", -1); + modifiedHabit.reminderDays = bundle.getInt("reminderDays", -1); + if (modifiedHabit.reminderMin < 0) modifiedHabit.clearReminder(); } + protected abstract void saveHabit(); + @OnClick(R.id.buttonPickColor) void showColorPicker() { - int androidColor = ColorUtils.getColor(getContext(), modifiedHabit.color); + int androidColor = + ColorUtils.getColor(getContext(), modifiedHabit.color); - ColorPickerDialog picker = ColorPickerDialog.newInstance( - R.string.color_picker_default_title, ColorUtils.getPalette(getContext()), - androidColor, 4, ColorPickerDialog.SIZE_SMALL); + ColorPickerDialog picker = + ColorPickerDialog.newInstance(R.string.color_picker_default_title, + ColorUtils.getPalette(getContext()), androidColor, 4, + ColorPickerDialog.SIZE_SMALL); picker.setOnColorSelectedListener(new OnColorSelectedListener()); picker.show(getFragmentManager(), "picker"); @@ -165,43 +181,36 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment private void showTimePicker(int defaultHour, int defaultMin) { boolean is24HourMode = DateFormat.is24HourFormat(getContext()); - TimePickerDialog timePicker = TimePickerDialog.newInstance(new OnTimeSetListener(), - defaultHour, defaultMin, is24HourMode); + TimePickerDialog timePicker = + TimePickerDialog.newInstance(new OnTimeSetListener(), defaultHour, + defaultMin, is24HourMode); timePicker.show(getFragmentManager(), "timePicker"); } - private class OnColorSelectedListener implements ColorPickerSwatch.OnColorSelectedListener + private class OnColorSelectedListener + implements ColorPickerSwatch.OnColorSelectedListener { + @Override public void onColorSelected(int androidColor) { - int paletteColor = ColorUtils.colorToPaletteIndex(getActivity(), androidColor); + int paletteColor = + ColorUtils.colorToPaletteIndex(getActivity(), androidColor); prefs.setDefaultHabitColor(paletteColor); modifiedHabit.color = paletteColor; helper.populateColor(paletteColor); } } - private class OnWeekdaysPickedListener implements WeekdayPickerDialog.OnWeekdaysPickedListener + private class OnTimeSetListener + implements TimePickerDialog.OnTimeSetListener { @Override - public void onWeekdaysPicked(boolean[] selectedDays) + public void onTimeCleared(RadialPickerLayout view) { - if(isSelectionEmpty(selectedDays)) - Arrays.fill(selectedDays, true); - - modifiedHabit.reminderDays = DateUtils.packWeekdayList(selectedDays); + modifiedHabit.clearReminder(); helper.populateReminderFields(modifiedHabit); } - private boolean isSelectionEmpty(boolean[] selectedDays) - { - for (boolean d : selectedDays) if (d) return false; - return true; - } - } - - private class OnTimeSetListener implements TimePickerDialog.OnTimeSetListener - { @Override public void onTimeSet(RadialPickerLayout view, int hour, int minute) { @@ -210,12 +219,25 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment modifiedHabit.reminderDays = DateUtils.ALL_WEEK_DAYS; helper.populateReminderFields(modifiedHabit); } + } + private class OnWeekdaysPickedListener + implements WeekdayPickerDialog.OnWeekdaysPickedListener + { @Override - public void onTimeCleared(RadialPickerLayout view) + public void onWeekdaysPicked(boolean[] selectedDays) { - modifiedHabit.clearReminder(); + if (isSelectionEmpty(selectedDays)) Arrays.fill(selectedDays, true); + + modifiedHabit.reminderDays = + DateUtils.packWeekdayList(selectedDays); helper.populateReminderFields(modifiedHabit); } + + private boolean isSelectionEmpty(boolean[] selectedDays) + { + for (boolean d : selectedDays) if (d) return false; + return true; + } } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java index 7605976c2..a9f518b48 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java @@ -37,15 +37,33 @@ import butterknife.ButterKnife; public class BaseDialogHelper { private DialogFragment frag; - @BindView(R.id.tvName) TextView tvName; - @BindView(R.id.tvDescription) TextView tvDescription; - @BindView(R.id.tvFreqNum) TextView tvFreqNum; - @BindView(R.id.tvFreqDen) TextView tvFreqDen; - @BindView(R.id.tvReminderTime) TextView tvReminderTime; - @BindView(R.id.tvReminderDays) TextView tvReminderDays; - @BindView(R.id.sFrequency) Spinner sFrequency; - @BindView(R.id.llCustomFrequency) ViewGroup llCustomFrequency; - @BindView(R.id.llReminderDays) ViewGroup llReminderDays; + + @BindView(R.id.tvName) + TextView tvName; + + @BindView(R.id.tvDescription) + TextView tvDescription; + + @BindView(R.id.tvFreqNum) + TextView tvFreqNum; + + @BindView(R.id.tvFreqDen) + TextView tvFreqDen; + + @BindView(R.id.tvReminderTime) + TextView tvReminderTime; + + @BindView(R.id.tvReminderDays) + TextView tvReminderDays; + + @BindView(R.id.sFrequency) + Spinner sFrequency; + + @BindView(R.id.llCustomFrequency) + ViewGroup llCustomFrequency; + + @BindView(R.id.llReminderDays) + ViewGroup llReminderDays; public BaseDialogHelper(DialogFragment frag, View view) { @@ -53,37 +71,30 @@ public class BaseDialogHelper ButterKnife.bind(this, view); } - protected void populateForm(final Habit habit) + void parseFormIntoHabit(Habit habit) { - if(habit.name != null) tvName.setText(habit.name); - if(habit.description != null) tvDescription.setText(habit.description); - - populateColor(habit.color); - populateFrequencyFields(habit); - populateReminderFields(habit); + habit.name = tvName.getText().toString().trim(); + habit.description = tvDescription.getText().toString().trim(); + String freqNum = tvFreqNum.getText().toString(); + String freqDen = tvFreqDen.getText().toString(); + if (!freqNum.isEmpty()) habit.freqNum = Integer.parseInt(freqNum); + if (!freqDen.isEmpty()) habit.freqDen = Integer.parseInt(freqDen); } void populateColor(int paletteColor) { - tvName.setTextColor(ColorUtils.getColor(frag.getContext(), paletteColor)); + tvName.setTextColor( + ColorUtils.getColor(frag.getContext(), paletteColor)); } - @SuppressWarnings("ConstantConditions") - void populateReminderFields(Habit habit) + protected void populateForm(final Habit habit) { - if (!habit.hasReminder()) - { - tvReminderTime.setText(R.string.reminder_off); - llReminderDays.setVisibility(View.GONE); - return; - } - - String time = DateUtils.formatTime(frag.getContext(), habit.reminderHour, habit.reminderMin); - tvReminderTime.setText(time); - llReminderDays.setVisibility(View.VISIBLE); + if (habit.name != null) tvName.setText(habit.name); + if (habit.description != null) tvDescription.setText(habit.description); - boolean weekdays[] = DateUtils.unpackWeekdayList(habit.reminderDays); - tvReminderDays.setText(DateUtils.formatWeekdayList(frag.getContext(), weekdays)); + populateColor(habit.color); + populateFrequencyFields(habit); + populateReminderFields(habit); } @SuppressLint("SetTextI18n") @@ -91,25 +102,47 @@ public class BaseDialogHelper { int quickSelectPosition = -1; - if(habit.freqNum.equals(habit.freqDen)) - quickSelectPosition = 0; + if (habit.freqNum.equals(habit.freqDen)) quickSelectPosition = 0; - else if(habit.freqNum == 1 && habit.freqDen == 7) + else if (habit.freqNum == 1 && habit.freqDen == 7) quickSelectPosition = 1; - else if(habit.freqNum == 2 && habit.freqDen == 7) + else if (habit.freqNum == 2 && habit.freqDen == 7) quickSelectPosition = 2; - else if(habit.freqNum == 5 && habit.freqDen == 7) + else if (habit.freqNum == 5 && habit.freqDen == 7) quickSelectPosition = 3; - if(quickSelectPosition >= 0) showSimplifiedFrequency(quickSelectPosition); + if (quickSelectPosition >= 0) + showSimplifiedFrequency(quickSelectPosition); + else showCustomFrequency(); tvFreqNum.setText(habit.freqNum.toString()); tvFreqDen.setText(habit.freqDen.toString()); } + @SuppressWarnings("ConstantConditions") + void populateReminderFields(Habit habit) + { + if (!habit.hasReminder()) + { + tvReminderTime.setText(R.string.reminder_off); + llReminderDays.setVisibility(View.GONE); + return; + } + + String time = + DateUtils.formatTime(frag.getContext(), habit.reminderHour, + habit.reminderMin); + tvReminderTime.setText(time); + llReminderDays.setVisibility(View.VISIBLE); + + boolean weekdays[] = DateUtils.unpackWeekdayList(habit.reminderDays); + tvReminderDays.setText( + DateUtils.formatWeekdayList(frag.getContext(), weekdays)); + } + private void showCustomFrequency() { sFrequency.setVisibility(View.GONE); @@ -130,32 +163,25 @@ public class BaseDialogHelper if (habit.name.length() == 0) { - tvName.setError(frag.getString(R.string.validation_name_should_not_be_blank)); + tvName.setError( + frag.getString(R.string.validation_name_should_not_be_blank)); valid = false; } if (habit.freqNum <= 0) { - tvFreqNum.setError(frag.getString(R.string.validation_number_should_be_positive)); + tvFreqNum.setError( + frag.getString(R.string.validation_number_should_be_positive)); valid = false; } if (habit.freqNum > habit.freqDen) { - tvFreqNum.setError(frag.getString(R.string.validation_at_most_one_rep_per_day)); + tvFreqNum.setError( + frag.getString(R.string.validation_at_most_one_rep_per_day)); valid = false; } return valid; } - - void parseFormIntoHabit(Habit habit) - { - habit.name = tvName.getText().toString().trim(); - habit.description = tvDescription.getText().toString().trim(); - String freqNum = tvFreqNum.getText().toString(); - String freqDen = tvFreqDen.getText().toString(); - if(!freqNum.isEmpty()) habit.freqNum = Integer.parseInt(freqNum); - if(!freqDen.isEmpty()) habit.freqDen = Integer.parseInt(freqDen); - } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java index 077a2c750..b63a95f24 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java @@ -21,7 +21,6 @@ package org.isoron.uhabits.ui.habits.edit; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.models.Habit; @@ -45,6 +44,6 @@ public class CreateHabitDialogFragment extends BaseDialogFragment protected void saveHabit() { Command command = new CreateHabitCommand(modifiedHabit); - CommandRunner.getInstance().execute(command, null); + commandRunner.execute(command, null); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java index 7ffff0aac..7adf8f3e8 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java @@ -23,7 +23,6 @@ import android.os.Bundle; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.EditHabitCommand; import org.isoron.uhabits.models.Habit; @@ -48,7 +47,8 @@ public class EditHabitDialogFragment extends BaseDialogFragment protected void initializeHabits() { Long habitId = (Long) getArguments().get("habitId"); - if(habitId == null) throw new IllegalArgumentException("habitId must be specified"); + if (habitId == null) + throw new IllegalArgumentException("habitId must be specified"); originalHabit = Habit.get(habitId); modifiedHabit = new Habit(originalHabit); @@ -57,6 +57,6 @@ public class EditHabitDialogFragment extends BaseDialogFragment protected void saveHabit() { Command command = new EditHabitCommand(originalHabit, modifiedHabit); - CommandRunner.getInstance().execute(command, originalHabit.getId()); + commandRunner.execute(command, originalHabit.getId()); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java index 147d62f63..f1bd5c1f8 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java @@ -36,7 +36,9 @@ public class HistoryEditorDialog extends AppCompatDialogFragment implements DialogInterface.OnClickListener { private Habit habit; + private Listener listener; + HabitHistoryView historyView; @Override @@ -45,21 +47,23 @@ public class HistoryEditorDialog extends AppCompatDialogFragment Context context = getActivity(); historyView = new HabitHistoryView(context, null); - if(savedInstanceState != null) + if (savedInstanceState != null) { long id = savedInstanceState.getLong("habit", -1); - if(id > 0) this.habit = Habit.get(id); + if (id > 0) this.habit = Habit.get(id); } - int padding = (int) getResources().getDimension(R.dimen.history_editor_padding); + int padding = + (int) getResources().getDimension(R.dimen.history_editor_padding); historyView.setPadding(padding, 0, padding, 0); historyView.setHabit(habit); historyView.setIsEditable(true); AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.history) - .setView(historyView) - .setPositiveButton(android.R.string.ok, this); + builder + .setTitle(R.string.history) + .setView(historyView) + .setPositiveButton(android.R.string.ok, this); refreshData(); @@ -84,7 +88,8 @@ public class HistoryEditorDialog extends AppCompatDialogFragment super.onResume(); DisplayMetrics metrics = getResources().getDisplayMetrics(); - int maxHeight = getResources().getDimensionPixelSize(R.dimen.history_editor_max_height); + int maxHeight = getResources().getDimensionPixelSize( + R.dimen.history_editor_max_height); int width = metrics.widthPixels; int height = Math.min(metrics.heightPixels, maxHeight); @@ -100,14 +105,14 @@ public class HistoryEditorDialog extends AppCompatDialogFragment public void setHabit(Habit habit) { this.habit = habit; - if(historyView != null) historyView.setHabit(habit); + if (historyView != null) historyView.setHabit(habit); } @Override public void onPause() { super.onPause(); - if(listener != null) listener.onHistoryEditorClosed(); + if (listener != null) listener.onHistoryEditorClosed(); } @Override @@ -121,7 +126,8 @@ public class HistoryEditorDialog extends AppCompatDialogFragment this.listener = listener; } - public interface Listener { + public interface Listener + { void onHistoryEditorClosed(); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java index b6af16b01..477c736f0 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/WeekdayPickerDialog.java @@ -28,8 +28,9 @@ import android.support.v7.app.AppCompatDialogFragment; import org.isoron.uhabits.R; import org.isoron.uhabits.utils.DateUtils; -public class WeekdayPickerDialog extends AppCompatDialogFragment - implements DialogInterface.OnMultiChoiceClickListener, DialogInterface.OnClickListener +public class WeekdayPickerDialog extends AppCompatDialogFragment implements + DialogInterface.OnMultiChoiceClickListener, + DialogInterface.OnClickListener { public interface OnWeekdaysPickedListener @@ -38,6 +39,7 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment } private boolean[] selectedDays; + private OnWeekdaysPickedListener listener; public void setListener(OnWeekdaysPickedListener listener) @@ -54,10 +56,13 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(R.string.select_weekdays) - .setMultiChoiceItems(DateUtils.getLongDayNames(), selectedDays, this) - .setPositiveButton(android.R.string.yes, this) - .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() + builder + .setTitle(R.string.select_weekdays) + .setMultiChoiceItems(DateUtils.getLongDayNames(), selectedDays, + this) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) @@ -78,6 +83,6 @@ public class WeekdayPickerDialog extends AppCompatDialogFragment @Override public void onClick(DialogInterface dialog, int which) { - if(listener != null) listener.onWeekdaysPicked(selectedDays); + if (listener != null) listener.onWeekdaysPicked(selectedDays); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java deleted file mode 100644 index e95aa3d11..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListAdapter.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.ui.habits.list; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; - -import java.util.List; - -class HabitListAdapter extends BaseAdapter -{ - private LayoutInflater inflater; - private HabitListLoader loader; - private ListHabitsHelper helper; - private List selectedPositions; - private View.OnLongClickListener onCheckmarkLongClickListener; - private View.OnClickListener onCheckmarkClickListener; - - public HabitListAdapter(Context context, HabitListLoader loader) - { - this.loader = loader; - - inflater = LayoutInflater.from(context); - helper = new ListHabitsHelper(context, loader); - } - - @Override - public int getCount() - { - return loader.habits.size(); - } - - @Override - public Habit getItem(int position) - { - return loader.habitsList.get(position); - } - - @Override - public long getItemId(int position) - { - return (getItem(position)).getId(); - } - - @Override - public View getView(int position, View view, ViewGroup parent) - { - final Habit habit = loader.habitsList.get(position); - boolean selected = selectedPositions.contains(position); - - if (view == null || (Long) view.getTag(R.id.timestamp_key) != DateUtils.getStartOfToday()) - { - view = helper.inflateHabitCard(inflater, onCheckmarkLongClickListener, - onCheckmarkClickListener); - } - - helper.updateHabitCard(view, habit, selected); - return view; - } - - public void setSelectedPositions(List selectedPositions) - { - this.selectedPositions = selectedPositions; - } - - public void setOnCheckmarkLongClickListener(View.OnLongClickListener listener) - { - this.onCheckmarkLongClickListener = listener; - } - - public void setOnCheckmarkClickListener(View.OnClickListener listener) - { - this.onCheckmarkClickListener = listener; - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java deleted file mode 100644 index d80b178fa..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListLoader.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.ui.habits.list; - -import android.support.annotation.Nullable; - -import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.BaseTask; - -import java.util.HashMap; -import java.util.List; - -public class HabitListLoader implements CommandRunner.Listener -{ - public interface Listener - { - void onLoadFinished(); - } - - private BaseTask currentFetchTask; - private int checkmarkCount; - - @Nullable - private Listener listener; - private Long lastLoadTimestamp; - - public HashMap habits; - public List habitsList; - public HashMap checkmarks; - public HashMap scores; - - boolean includeArchived; - - public void setIncludeArchived(boolean includeArchived) - { - this.includeArchived = includeArchived; - } - - public void setCheckmarkCount(int checkmarkCount) - { - this.checkmarkCount = checkmarkCount; - } - - public void setListener(@Nullable Listener listener) - { - this.listener = listener; - } - - public Long getLastLoadTimestamp() - { - return lastLoadTimestamp; - } - - public HabitListLoader() - { - habits = new HashMap<>(); - checkmarks = new HashMap<>(); - scores = new HashMap<>(); - } - - public void reorder(int from, int to) - { - Habit fromHabit = habitsList.get(from); - Habit toHabit = habitsList.get(to); - - habitsList.remove(from); - habitsList.add(to, fromHabit); - - Habit.reorder(fromHabit, toHabit); - } - - public void updateAllHabits(final boolean updateScoresAndCheckmarks) - { - if (currentFetchTask != null) currentFetchTask.cancel(true); - - currentFetchTask = new BaseTask() - { - public HashMap newHabits; - public HashMap newCheckmarks; - public HashMap newScores; - public List newHabitList; - - @Override - protected void doInBackground() - { - newHabits = new HashMap<>(); - newCheckmarks = new HashMap<>(); - newScores = new HashMap<>(); - newHabitList = Habit.getAll(includeArchived); - - long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); - long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; - int[] empty = new int[checkmarkCount]; - - for(Habit h : newHabitList) - { - Long id = h.getId(); - - newHabits.put(id, h); - - if(checkmarks.containsKey(id)) - newCheckmarks.put(id, checkmarks.get(id)); - else - newCheckmarks.put(id, empty); - - if(scores.containsKey(id)) - newScores.put(id, scores.get(id)); - else - newScores.put(id, 0); - } - - commit(); - - if(!updateScoresAndCheckmarks) return; - - int current = 0; - for (Habit h : newHabitList) - { - if (isCancelled()) return; - - Long id = h.getId(); - newScores.put(id, h.scores.getTodayValue()); - newCheckmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo)); - - publishProgress(current++, newHabits.size()); - } - } - - private void commit() - { - habits = newHabits; - scores = newScores; - checkmarks = newCheckmarks; - habitsList = newHabitList; - } - - @Override - protected void onProgressUpdate(Integer... values) - { - if(listener != null) listener.onLoadFinished(); - } - - @Override - protected void onPostExecute(Void aVoid) - { - if (isCancelled()) return; - - lastLoadTimestamp = DateUtils.getStartOfToday(); - currentFetchTask = null; - - if(listener != null) listener.onLoadFinished(); - super.onPostExecute(null); - } - - }; - - currentFetchTask.execute(); - } - - public void updateHabit(final Long id) - { - new BaseTask() - { - @Override - protected void doInBackground() - { - long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); - long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; - - Habit h = Habit.get(id); - if(h == null) return; - - habits.put(id, h); - scores.put(id, h.scores.getTodayValue()); - checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo)); - } - - @Override - protected void onPostExecute(Void aVoid) - { - if(listener != null) listener.onLoadFinished(); - super.onPostExecute(null); - } - }.execute(); - } - - public void onResume() - { - CommandRunner.getInstance().addListener(this); - } - - public void onPause() - { - CommandRunner.getInstance().removeListener(this); - } - - @Override - public void onCommandExecuted(Command command, Long refreshKey) - { - if(refreshKey == null) updateAllHabits(true); - else updateHabit(refreshKey); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java deleted file mode 100644 index f292ff978..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListSelectionCallback.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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.ui.habits.list; - -import android.content.DialogInterface; -import android.support.v7.app.AlertDialog; -import android.support.v7.view.ActionMode; -import android.view.Menu; -import android.view.MenuItem; - -import com.android.colorpicker.ColorPickerDialog; -import com.android.colorpicker.ColorPickerSwatch; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.ArchiveHabitsCommand; -import org.isoron.uhabits.commands.ChangeHabitColorCommand; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.commands.DeleteHabitsCommand; -import org.isoron.uhabits.commands.UnarchiveHabitsCommand; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; -import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; -import org.isoron.uhabits.utils.ColorUtils; - -import java.util.LinkedList; -import java.util.List; - -public class HabitListSelectionCallback implements ActionMode.Callback -{ - private HabitListLoader loader; - private List selectedPositions; - private BaseActivity activity; - private Listener listener; - - public interface Listener - { - void onActionModeDestroyed(ActionMode mode); - } - - public HabitListSelectionCallback(BaseActivity activity, HabitListLoader loader) - { - this.activity = activity; - this.loader = loader; - selectedPositions = new LinkedList<>(); - } - - public void setListener(Listener listener) - { - this.listener = listener; - } - - public void setSelectedPositions(List selectedPositions) - { - this.selectedPositions = selectedPositions; - } - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) - { - activity.getMenuInflater().inflate(R.menu.list_habits_selection, menu); - updateTitle(mode); - updateActions(menu); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) - { - updateTitle(mode); - updateActions(menu); - return true; - } - - private void updateActions(Menu menu) - { - boolean showEdit = (selectedPositions.size() == 1); - boolean showArchive = true; - boolean showUnarchive = true; - for (int i : selectedPositions) - { - Habit h = loader.habitsList.get(i); - if (h.isArchived()) showArchive = false; - else showUnarchive = false; - } - - MenuItem itemEdit = menu.findItem(R.id.action_edit_habit); - MenuItem itemColor = menu.findItem(R.id.action_color); - MenuItem itemArchive = menu.findItem(R.id.action_archive_habit); - MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit); - - itemColor.setVisible(true); - itemEdit.setVisible(showEdit); - itemArchive.setVisible(showArchive); - itemUnarchive.setVisible(showUnarchive); - } - - private void updateTitle(ActionMode mode) - { - mode.setTitle("" + selectedPositions.size()); - } - - @Override - public boolean onActionItemClicked(final ActionMode mode, MenuItem item) - { - final LinkedList selectedHabits = new LinkedList<>(); - for (int i : selectedPositions) - selectedHabits.add(loader.habitsList.get(i)); - - Habit firstHabit = selectedHabits.getFirst(); - - switch (item.getItemId()) - { - case R.id.action_archive_habit: - archiveHabits(selectedHabits); - mode.finish(); - return true; - - case R.id.action_unarchive_habit: - unarchiveHabits(selectedHabits); - mode.finish(); - return true; - - case R.id.action_edit_habit: - { - editHabit(firstHabit); - mode.finish(); - return true; - } - - case R.id.action_color: - { - showColorPicker(mode, selectedHabits, firstHabit); - mode.finish(); - return true; - } - - case R.id.action_delete: - { - deleteHabits(mode, selectedHabits); - mode.finish(); - return true; - } - } - - return false; - } - - private void deleteHabits(final ActionMode mode, final LinkedList selectedHabits) - { - new AlertDialog.Builder(activity).setTitle(R.string.delete_habits) - .setMessage(R.string.delete_habits_message) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - CommandRunner.getInstance() - .execute(new DeleteHabitsCommand(selectedHabits), null); - mode.finish(); - } - }) - .setNegativeButton(android.R.string.no, null) - .show(); - } - - private void showColorPicker(final ActionMode mode, final LinkedList selectedHabits, - Habit firstHabit) - { - int originalAndroidColor = ColorUtils.getColor(activity, firstHabit.color); - - ColorPickerDialog picker = - ColorPickerDialog.newInstance(R.string.color_picker_default_title, - ColorUtils.getPalette(activity), originalAndroidColor, 4, - ColorPickerDialog.SIZE_SMALL); - - picker.setOnColorSelectedListener(new ColorPickerSwatch.OnColorSelectedListener() - { - public void onColorSelected(int androidColor) - { - int paletteColor = ColorUtils.colorToPaletteIndex(activity, androidColor); - CommandRunner.getInstance() - .execute(new ChangeHabitColorCommand(selectedHabits, paletteColor), null); - mode.finish(); - } - }); - picker.show(activity.getSupportFragmentManager(), "picker"); - } - - private void editHabit(Habit habit) - { - BaseDialogFragment - frag = EditHabitDialogFragment.newInstance(habit.getId()); - frag.show(activity.getSupportFragmentManager(), "editHabit"); - } - - private void unarchiveHabits(LinkedList selectedHabits) - { - CommandRunner.getInstance().execute(new UnarchiveHabitsCommand(selectedHabits), null); - } - - private void archiveHabits(LinkedList selectedHabits) - { - CommandRunner.getInstance().execute(new ArchiveHabitsCommand(selectedHabits), null); - } - - @Override - public void onDestroyActionMode(ActionMode mode) - { - if(listener != null) listener.onActionModeDestroyed(mode); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java deleted file mode 100644 index 606ae3527..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/HabitListView.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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.ui.habits.list; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.view.HapticFeedbackConstants; -import android.view.View; -import android.widget.AdapterView; - -import com.mobeta.android.dslv.DragSortController; -import com.mobeta.android.dslv.DragSortListView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.Preferences; - -import java.util.Date; -import java.util.LinkedList; -import java.util.List; - -public class HabitListView extends DragSortListView implements View.OnClickListener, - View.OnLongClickListener, DragSortListView.DropListener, AdapterView.OnItemClickListener, - AdapterView.OnItemLongClickListener, DragSortListView.DragListener, HabitListLoader.Listener -{ - private final HabitListLoader loader; - private final HabitListAdapter adapter; - private final ListHabitsHelper helper; - private final Preferences prefs; - private final List selectedPositions; - - @Nullable - private Listener listener; - private long lastLongClick; - private boolean showArchived; - - public HabitListView(Context context, AttributeSet attrs) - { - super(context, attrs); - - loader = new HabitListLoader(); - adapter = new HabitListAdapter(context, loader); - selectedPositions = new LinkedList<>(); - prefs = Preferences.getInstance(); - helper = new ListHabitsHelper(getContext(), loader); - - adapter.setSelectedPositions(selectedPositions); - adapter.setOnCheckmarkClickListener(this); - adapter.setOnCheckmarkLongClickListener(this); - loader.setListener(this); - loader.setCheckmarkCount(helper.getButtonCount()); - - setAdapter(adapter); - setOnItemClickListener(this); - setOnItemLongClickListener(this); - setDropListener(this); - setDragListener(this); - setFloatViewManager(new HabitsDragSortController()); - setDragEnabled(false); - setLongClickable(true); - } - - public HabitListLoader getLoader() - { - return loader; - } - - public List getSelectedPositions() - { - return selectedPositions; - } - - public void setListener(@Nullable Listener l) - { - this.listener = l; - } - - @Override - public void drop(int from, int to) - { - if(from == to) return; - cancelSelection(); - - loader.reorder(from, to); - adapter.notifyDataSetChanged(); - loader.updateAllHabits(false); - } - - @Override - public void onClick(View v) - { - if (v.getId() != R.id.tvCheck) return; - - if (prefs.isShortToggleEnabled()) toggleCheckmark(v); - else if(listener != null) listener.onInvalidToggle(); - } - - @Override - public boolean onLongClick(View v) - { - lastLongClick = new Date().getTime(); - if (v.getId() != R.id.tvCheck) return true; - if (prefs.isShortToggleEnabled()) return true; - toggleCheckmark(v); - return true; - } - - public void toggleShowArchived() - { - showArchived = !showArchived; - loader.setIncludeArchived(showArchived); - loader.updateAllHabits(true); - } - - private void toggleCheckmark(View v) - { - Long id = helper.getHabitIdFromCheckmarkView(v); - Habit habit = loader.habits.get(id); - if(habit == null) return; - - float x = v.getX() + v.getWidth() / 2.0f + ((View) v.getParent()).getX(); - float y = v.getY() + v.getHeight() / 2.0f + ((View) v.getParent()).getY(); - helper.triggerRipple((View) v.getParent().getParent(), x, y); - - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - helper.toggleCheckmarkView(v, habit); - - long timestamp = helper.getTimestampFromCheckmarkView(v); - - if(listener != null) listener.onToggleCheckmark(habit, timestamp); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) - { - if (new Date().getTime() - lastLongClick < 1000) return; - - if(selectedPositions.isEmpty()) - { - Habit habit = loader.habitsList.get(position); - if(listener != null) listener.onHabitClick(habit); - } - else - { - toggleItemSelected(position); - adapter.notifyDataSetChanged(); - } - } - - private void toggleItemSelected(int position) - { - int k = selectedPositions.indexOf(position); - if(k < 0) selectedPositions.add(position); - else selectedPositions.remove(k); - - if(listener != null) - { - if (selectedPositions.isEmpty()) listener.onHabitSelectionFinish(); - else listener.onHabitSelectionChange(); - } - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) - { - selectHabit(position); - return true; - } - - private void selectHabit(int position) - { - if(!selectedPositions.contains(position)) selectedPositions.add(position); - adapter.notifyDataSetChanged(); - - if(listener != null) - { - if (selectedPositions.size() == 1) listener.onHabitSelectionStart(); - else listener.onHabitSelectionChange(); - } - } - - @Override - public void drag(int from, int to) - { - } - - @Override - public void startDrag(int position) - { - selectHabit(position); - } - - public boolean getShowArchived() - { - return showArchived; - } - - public void cancelSelection() - { - selectedPositions.clear(); - adapter.notifyDataSetChanged(); - setDragEnabled(true); - if(listener != null) listener.onHabitSelectionFinish(); - } - - public void refreshData(Long refreshKey) - { - if (refreshKey == null) loader.updateAllHabits(true); - else loader.updateHabit(refreshKey); - } - - @Override - public void onLoadFinished() - { - adapter.notifyDataSetChanged(); - if(listener != null) listener.onDatasetChanged(); - } - - private class HabitsDragSortController extends DragSortController - { - public HabitsDragSortController() - { - super(HabitListView.this); - setRemoveEnabled(false); - } - - @Override - public View onCreateFloatView(int position) - { - return adapter.getView(position, null, null); - } - - @Override - public void onDestroyFloatView(View floatView) - { - } - } - - public interface Listener - { - void onToggleCheckmark(Habit habit, long timestamp); - - void onHabitClick(Habit habit); - - void onHabitSelectionStart(); - - void onHabitSelectionFinish(); - - void onHabitSelectionChange(); - - void onInvalidToggle(); - - void onDatasetChanged(); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java new file mode 100644 index 000000000..a111df442 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java @@ -0,0 +1,46 @@ +/* + * 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.ui.habits.list; + +import android.os.Bundle; + +import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.BaseSystem; + +/** + * Activity that allows the user to see and modify the list of habits. + */ +public class ListHabitsActivity extends BaseActivity +{ + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + BaseSystem system = new BaseSystem(this); + ListHabitsScreen screen = new ListHabitsScreen(this); + ListHabitsController controller = + new ListHabitsController(screen, system); + + screen.setController(controller); + + setScreen(screen); + controller.onStartup(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java index 25557f553..b5c91775e 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java @@ -19,18 +19,162 @@ package org.isoron.uhabits.ui.habits.list; +import android.support.annotation.NonNull; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.R; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.commands.ToggleRepetitionCommand; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.ExportCSVTask; +import org.isoron.uhabits.tasks.ExportDBTask; +import org.isoron.uhabits.tasks.ImportDataTask; +import org.isoron.uhabits.ui.BaseSystem; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.Preferences; + +import java.io.File; +import java.io.IOException; + +import javax.inject.Inject; + public class ListHabitsController + implements ImportDataTask.Listener, HabitCardListController.HabitListener { - public interface Screen + @NonNull + private final ListHabitsScreen screen; + + @NonNull + private final BaseSystem system; + + @Inject + Preferences prefs; + + @Inject + CommandRunner commandRunner; + + public ListHabitsController(@NonNull ListHabitsScreen screen, + @NonNull BaseSystem system) { + this.screen = screen; + this.system = system; + HabitsApplication.getComponent().inject(this); + } + public void onExportCSV() + { + ExportCSVTask task = + new ExportCSVTask(Habit.getAll(true), screen.getProgressBar()); + task.setListener(filename -> { + if (filename != null) screen.showSendFileScreen(filename); + else screen.showMessage(R.string.could_not_export); + }); + task.execute(); } - private Screen screen; + public void onExportDB() + { + ExportDBTask task = new ExportDBTask(screen.getProgressBar()); + task.setListener(filename -> { + if (filename != null) screen.showSendFileScreen(filename); + else screen.showMessage(R.string.could_not_export); + }); + task.execute(); + } - public void setScreen(Screen screen) + @Override + public void onHabitClick(@NonNull Habit h) { - this.screen = screen; + screen.showHabitScreen(h); + } + + @Override + public void onHabitReorder(@NonNull Habit from, @NonNull Habit to) + { + Habit.reorder(from, to); } + public void onImportData(File file) + { + ImportDataTask task = new ImportDataTask(file, screen.getProgressBar()); + task.setListener(this); + task.execute(); + } + + @Override + public void onImportDataFinished(int result) + { + switch (result) + { + case ImportDataTask.SUCCESS: + screen.invalidate(); + screen.showMessage(R.string.habits_imported); + break; + + case ImportDataTask.NOT_RECOGNIZED: + screen.showMessage(R.string.file_not_recognized); + break; + + default: + screen.showMessage(R.string.could_not_import); + break; + } + } + + @Override + public void onInvalidToggle() + { + screen.showMessage(R.string.long_press_to_toggle); + } + + public void onSendBugReport() + { + try + { + system.dumpBugReportToFile(); + } catch (IOException e) + { + // ignored + } + + try + { + String log = "---------- BUG REPORT BEGINS ----------\n"; + log += system.getBugReport(); + log += "---------- BUG REPORT ENDS ------------\n"; + String to = "dev@loophabits.org"; + String subject = "Bug Report - Loop Habit Tracker"; + screen.showSendEmailScreen(log, to, subject); + } catch (IOException e) + { + e.printStackTrace(); + screen.showMessage(R.string.bug_report_failed); + } + } + + public void onStartup() + { + prefs.initialize(); + prefs.incrementLaunchCount(); + prefs.updateLastAppVersion(); + if (prefs.isFirstRun()) onFirstRun(); + + system.updateWidgets(); + system.scheduleReminders(); + } + + @Override + public void onToggle(@NonNull Habit habit, long timestamp) + { + commandRunner.execute(new ToggleRepetitionCommand(habit, timestamp), + null); + } + + private void onFirstRun() + { + prefs.setFirstRun(false); + prefs.updateLastHint(-1, DateUtils.getStartOfToday()); + screen.showIntroScreen(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java deleted file mode 100644 index ce7b6d541..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsFragment.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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.ui.habits.list; - -import android.app.Activity; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.view.ActionMode; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.ui.HintManager; -import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; -import org.isoron.uhabits.ui.habits.edit.CreateHabitDialogFragment; -import org.isoron.uhabits.utils.InterfaceUtils; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; - -public class ListHabitsFragment extends Fragment - implements HabitListSelectionCallback.Listener, ListHabitsController.Screen -{ - private ActionMode actionMode; - private HintManager hintManager; - private ListHabitsHelper helper; - private Listener habitClickListener; - private BaseActivity activity; - - @BindView(R.id.listView) HabitListView listView; - @BindView(R.id.llButtonsHeader) LinearLayout llButtonsHeader; - @BindView(R.id.progressBar) ProgressBar progressBar; - @BindView(R.id.llEmpty) View llEmpty; - @BindView(R.id.llHint) View llHint; - @BindView(R.id.tvStarEmpty) TextView tvStarEmpty; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - View view = inflater.inflate(R.layout.list_habits_fragment, container, false); - ButterKnife.bind(this, view); - - helper = new ListHabitsHelper(activity, listView.getLoader()); - hintManager = new HintManager(activity, llHint); - tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(activity)); - listView.setListener(new HabitListViewListener()); - setHasOptionsMenu(true); - - return view; - } - - @Override - @SuppressWarnings("deprecation") - public void onAttach(Activity activity) - { - super.onAttach(activity); - this.activity = (BaseActivity) activity; - habitClickListener = (Listener) activity; - } - - @Override - public void onResume() - { - super.onResume(); - listView.getLoader().onResume(); - listView.refreshData(null); - helper.updateEmptyMessage(llEmpty); - helper.updateHeader(llButtonsHeader); - hintManager.showHintIfAppropriate(); - } - - @Override - public void onPause() - { - listView.getLoader().onPause(); - super.onPause(); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.list_habits_fragment, menu); - MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived); - showArchivedItem.setChecked(listView.getShowArchived()); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - switch (item.getItemId()) - { - case R.id.action_add: - showCreateHabitScreen(); - return true; - - case R.id.action_show_archived: - toggleShowArchived(); - return true; - - default: - return super.onOptionsItemSelected(item); - } - } - - private void toggleShowArchived() - { - listView.toggleShowArchived(); - activity.invalidateOptionsMenu(); - } - - private void showCreateHabitScreen() - { - BaseDialogFragment frag = new CreateHabitDialogFragment(); - frag.show(getFragmentManager(), "editHabit"); - } - - private void startActionMode() - { - HabitListSelectionCallback callback = - new HabitListSelectionCallback(activity, listView.getLoader()); - callback.setSelectedPositions(listView.getSelectedPositions()); - callback.setListener(this); - actionMode = activity.startSupportActionMode(callback); - } - - private void finishActionMode() - { - if(actionMode != null) actionMode.finish(); - } - - @Override - public void onActionModeDestroyed(ActionMode mode) - { - actionMode = null; - listView.cancelSelection(); - } - - @OnClick(R.id.llHint) - public void onClickHint() - { - hintManager.dismissHint(); - } - - public ProgressBar getProgressBar() - { - return progressBar; - } - - public void refresh(Long refreshKey) - { - listView.refreshData(refreshKey); - } - - public interface Listener - { - void onHabitClick(Habit habit); - } - - private class HabitListViewListener implements HabitListView.Listener - { - @Override - public void onToggleCheckmark(Habit habit, long timestamp) - { - CommandRunner.getInstance().execute(new ToggleRepetitionCommand(habit, timestamp), - habit.getId()); - } - - @Override - public void onHabitClick(Habit habit) - { - habitClickListener.onHabitClick(habit); - } - - @Override - public void onHabitSelectionStart() - { - if(actionMode == null) startActionMode(); - } - - @Override - public void onHabitSelectionFinish() - { - finishActionMode(); - } - - @Override - public void onHabitSelectionChange() - { - if(actionMode != null) actionMode.invalidate(); - } - - @Override - public void onInvalidToggle() - { - activity.showMessage(R.string.long_press_to_toggle); - } - - @Override - public void onDatasetChanged() - { - helper.updateEmptyMessage(llEmpty); - } - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java deleted file mode 100644 index c8ab3fcac..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsHelper.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * 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.ui.habits.list; - -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.views.RingView; - -import java.util.GregorianCalendar; - -public class ListHabitsHelper -{ - private static final int CHECKMARK_LEFT_TO_RIGHT = 0; - private static final int CHECKMARK_RIGHT_TO_LEFT = 1; - - private final int lowContrastColor; - private final int mediumContrastColor; - - private final Context context; - private final HabitListLoader loader; - - public ListHabitsHelper(Context context, HabitListLoader loader) - { - this.context = context; - this.loader = loader; - - lowContrastColor = InterfaceUtils.getStyledColor(context, R.attr.lowContrastTextColor); - mediumContrastColor = InterfaceUtils.getStyledColor(context, R.attr.mediumContrastTextColor); - } - - public int getButtonCount() - { - float screenWidth = InterfaceUtils.getScreenWidth(context); - float labelWidth = context.getResources().getDimension(R.dimen.habitNameWidth); - float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth); - return Math.max(0, (int) ((screenWidth - labelWidth) / buttonWidth)); - } - - public int getHabitNameWidth() - { - float screenWidth = InterfaceUtils.getScreenWidth(context); - float buttonWidth = context.getResources().getDimension(R.dimen.checkmarkWidth); - float padding = InterfaceUtils.dpToPixels(context, 15); - return (int) (screenWidth - padding - getButtonCount() * buttonWidth); - } - - public void updateCheckmarkButtons(Habit habit, LinearLayout llButtons) - { - int activeColor = getActiveColor(habit); - int m = llButtons.getChildCount(); - Long habitId = habit.getId(); - - int isChecked[] = loader.checkmarks.get(habitId); - - for (int i = 0; i < m; i++) - { - int position = i; - - if(getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT) - position = m - i - 1; - - TextView tvCheck = (TextView) llButtons.getChildAt(position); - tvCheck.setTag(R.string.habit_key, habitId); - tvCheck.setTag(R.string.offset_key, i); - if(isChecked.length > i) - updateCheckmark(activeColor, tvCheck, isChecked[i]); - } - } - - public int getActiveColor(Habit habit) - { - int activeColor = ColorUtils.getColor(context, habit.color); - if(habit.isArchived()) activeColor = mediumContrastColor; - - return activeColor; - } - - public void initializeLabelAndIcon(View itemView) - { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getHabitNameWidth(), - LinearLayout.LayoutParams.WRAP_CONTENT, 1); - itemView.findViewById(R.id.label).setLayoutParams(params); - } - - public void updateNameAndIcon(Habit habit, RingView ring, TextView tvName) - { - int activeColor = getActiveColor(habit); - - tvName.setText(habit.name); - tvName.setTextColor(activeColor); - - int score = loader.scores.get(habit.getId()); - float percentage = (float) score / Score.MAX_VALUE; - - ring.setColor(activeColor); - ring.setPercentage(percentage); - ring.setPrecision(1.0f / 16); - } - - public void updateCheckmark(int activeColor, TextView tvCheck, int check) - { - switch (check) - { - case 2: - tvCheck.setText(R.string.fa_check); - tvCheck.setTextColor(activeColor); - tvCheck.setTag(R.string.toggle_key, 2); - break; - - case 1: - tvCheck.setText(R.string.fa_check); - tvCheck.setTextColor(lowContrastColor); - tvCheck.setTag(R.string.toggle_key, 1); - break; - - case 0: - tvCheck.setText(R.string.fa_times); - tvCheck.setTextColor(lowContrastColor); - tvCheck.setTag(R.string.toggle_key, 0); - break; - } - } - - public View inflateHabitCard(LayoutInflater inflater, - View.OnLongClickListener onCheckmarkLongClickListener, - View.OnClickListener onCheckmarkClickListener) - { - View view = inflater.inflate(R.layout.list_habits_item, null); - initializeLabelAndIcon(view); - inflateCheckmarkButtons(view, onCheckmarkLongClickListener, onCheckmarkClickListener, - inflater); - return view; - } - - public void updateHabitCard(View view, Habit habit, boolean selected) - { - RingView scoreRing = ((RingView) view.findViewById(R.id.scoreRing)); - TextView tvName = (TextView) view.findViewById(R.id.label); - LinearLayout llInner = (LinearLayout) view.findViewById(R.id.llInner); - LinearLayout llButtons = (LinearLayout) view.findViewById(R.id.llButtons); - - llInner.setTag(R.string.habit_key, habit.getId()); - llInner.setOnTouchListener(new HotspotTouchListener()); - - updateNameAndIcon(habit, scoreRing, tvName); - updateCheckmarkButtons(habit, llButtons); - updateHabitCardBackground(llInner, selected); - } - - - public void updateHabitCardBackground(View view, boolean isSelected) - { - if (android.os.Build.VERSION.SDK_INT >= 21) - { - if (isSelected) - view.setBackgroundResource(R.drawable.selected_box); - else - view.setBackgroundResource(R.drawable.ripple); - } - else - { - Drawable background; - - if (isSelected) - background = InterfaceUtils.getStyledDrawable(context, R.attr.selectedBackground); - else - background = InterfaceUtils.getStyledDrawable(context, R.attr.cardBackground); - - view.setBackgroundDrawable(background); - } - } - - public void inflateCheckmarkButtons(View view, View.OnLongClickListener onLongClickListener, - View.OnClickListener onClickListener, LayoutInflater inflater) - { - for (int i = 0; i < getButtonCount(); i++) - { - View check = inflater.inflate(R.layout.list_habits_item_check, null); - TextView btCheck = (TextView) check.findViewById(R.id.tvCheck); - btCheck.setTypeface(InterfaceUtils.getFontAwesome(context)); - btCheck.setOnLongClickListener(onLongClickListener); - btCheck.setOnClickListener(onClickListener); - btCheck.setHapticFeedbackEnabled(false); - ((LinearLayout) view.findViewById(R.id.llButtons)).addView(check); - } - - view.setTag(R.id.timestamp_key, DateUtils.getStartOfToday()); - } - - public void updateHeader(ViewGroup header) - { - LayoutInflater inflater = LayoutInflater.from(context); - GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); - header.removeAllViews(); - - for (int i = 0; i < getButtonCount(); i++) - { - int position = 0; - - if(getCheckmarkOrder() == CHECKMARK_LEFT_TO_RIGHT) - position = i; - - View tvDay = inflater.inflate(R.layout.list_habits_header_check, null); - TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck); - btCheck.setText(DateUtils.formatHeaderDate(day)); - header.addView(tvDay, position); - day.add(GregorianCalendar.DAY_OF_MONTH, -1); - } - } - - public void updateEmptyMessage(View view) - { - if (loader.getLastLoadTimestamp() == null) view.setVisibility(View.GONE); - else view.setVisibility(loader.habits.size() > 0 ? View.GONE : View.VISIBLE); - } - - public void toggleCheckmarkView(View v, Habit habit) - { - int androidColor = ColorUtils.getColor(context, habit.color); - - if (v.getTag(R.string.toggle_key).equals(2)) - updateCheckmark(androidColor, (TextView) v, 0); - else - updateCheckmark(androidColor, (TextView) v, 2); - } - - public Long getHabitIdFromCheckmarkView(View v) - { - return (Long) v.getTag(R.string.habit_key); - } - - public long getTimestampFromCheckmarkView(View v) - { - Integer offset = (Integer) v.getTag(R.string.offset_key); - return DateUtils.getStartOfDay(DateUtils.getLocalTime() - - offset * DateUtils.millisecondsInOneDay); - } - - public void triggerRipple(View v, final float x, final float y) - { - final Drawable background = v.getBackground(); - if (android.os.Build.VERSION.SDK_INT >= 21) - background.setHotspot(x, y); - - background.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled}); - - new Handler().postDelayed(new Runnable() - { - @Override - public void run() - { - background.setState(new int[]{}); - } - }, 25); - } - - private static class HotspotTouchListener implements View.OnTouchListener - { - @Override - public boolean onTouch(View v, MotionEvent event) - { - if (android.os.Build.VERSION.SDK_INT >= 21) - v.getBackground().setHotspot(event.getX(), event.getY()); - return false; - } - } - - public int getCheckmarkOrder() - { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean reverse = prefs.getBoolean("pref_checkmark_reverse_order", false); - return reverse ? CHECKMARK_RIGHT_TO_LEFT : CHECKMARK_LEFT_TO_RIGHT; - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsMenu.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsMenu.java new file mode 100644 index 000000000..8349499f9 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsMenu.java @@ -0,0 +1,96 @@ +/* + * 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.ui.habits.list; + +import android.support.annotation.NonNull; +import android.view.Menu; +import android.view.MenuItem; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.BaseMenu; +import org.isoron.uhabits.utils.InterfaceUtils; + +public class ListHabitsMenu extends BaseMenu +{ + @NonNull + private final ListHabitsScreen screen; + + private boolean showArchived; + + public ListHabitsMenu(@NonNull BaseActivity activity, + @NonNull ListHabitsScreen screen) + { + super(activity); + this.screen = screen; + } + + @Override + public void onCreate(@NonNull Menu menu) + { + MenuItem nightModeItem = menu.findItem(R.id.action_night_mode); + nightModeItem.setChecked(InterfaceUtils.isNightMode()); + + MenuItem showArchivedItem = menu.findItem(R.id.action_show_archived); + showArchivedItem.setChecked(showArchived); + } + + @Override + public boolean onItemSelected(@NonNull MenuItem item) + { + switch (item.getItemId()) + { + case R.id.action_night_mode: + screen.toggleNightMode(); + return true; + + case R.id.action_add: + screen.showCreateHabitScreen(); + return true; + + case R.id.action_faq: + screen.showFAQScreen(); + return true; + + case R.id.action_about: + screen.showAboutScreen(); + return true; + + case R.id.action_settings: + screen.showSettingsScreen(); + return true; + + case R.id.action_show_archived: + showArchived = !showArchived; + screen.getRootView().setShowArchived(showArchived); + invalidate(); + return true; + + default: + return false; + } + } + + @Override + protected int getMenuResourceId() + { + return R.menu.main_activity; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java new file mode 100644 index 000000000..83c634122 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java @@ -0,0 +1,173 @@ +/* + * 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.ui.habits.list; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.ui.BaseRootView; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.isoron.uhabits.ui.habits.list.model.HintList; +import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; +import org.isoron.uhabits.ui.habits.list.views.HintView; +import org.isoron.uhabits.utils.InterfaceUtils; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class ListHabitsRootView extends BaseRootView + implements ModelObservable.Listener +{ + @BindView(R.id.listView) + HabitCardListView listView; + + @BindView(R.id.llEmpty) + ViewGroup llEmpty; + + @BindView(R.id.tvStarEmpty) + TextView tvStarEmpty; + + @BindView(R.id.toolbar) + Toolbar toolbar; + + @BindView(R.id.progressBar) + ProgressBar progressBar; + + @BindView(R.id.hintView) + HintView hintView; + + @Nullable + private HabitCardListAdapter listAdapter; + + public ListHabitsRootView(@NonNull Context context) + { + super(context); + init(); + } + + @Override + @NonNull + public ProgressBar getProgressBar() + { + return progressBar; + } + + public boolean getShowArchived() + { + if(listAdapter == null) return false; + return listAdapter.getIncludeArchived(); + } + + @NonNull + @Override + public Toolbar getToolbar() + { + return toolbar; + } + + @Override + public int getToolbarColor() + { + return InterfaceUtils.getStyledColor(getContext(), R.attr.colorPrimary); + } + + @Override + public void onModelChange() + { + updateEmptyView(); + } + + public void setShowArchived(boolean showArchived) + { + if(listAdapter == null) return; + listAdapter.setShowArchived(showArchived); + } + + private void updateEmptyView() + { + if (listAdapter == null) return; + llEmpty.setVisibility( + listAdapter.getCount() > 0 ? View.GONE : View.VISIBLE); + } + + public void setController(@Nullable ListHabitsController controller, + @Nullable ListHabitsSelectionMenu menu) + { + listView.setController(null); + if (controller == null || listAdapter == null) return; + + HabitCardListController listController = + new HabitCardListController(listAdapter, listView); + listController.setHabitListener(controller); + listController.setSelectionListener(menu); + listView.setController(listController); + } + + public void setListAdapter(@NonNull HabitCardListAdapter listAdapter) + { + if (this.listAdapter != null) + listAdapter.getObservable().removeListener(this); + + this.listAdapter = listAdapter; + listView.setAdapter(listAdapter); + listAdapter.setListView(listView); + } + + private void init() + { + addView(inflate(getContext(), R.layout.list_habits, null)); + ButterKnife.bind(this); + + tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(getContext())); + initToolbar(); + + String hints[] = + getContext().getResources().getStringArray(R.array.hints); + HintList hintList = new HintList(hints); + hintView.setHints(hintList); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + + updateEmptyView(); + + if (listAdapter != null) listAdapter.getObservable().addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + if (listAdapter != null) + listAdapter.getObservable().removeListener(this); + super.onDetachedFromWindow(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java new file mode 100644 index 000000000..760233e37 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java @@ -0,0 +1,236 @@ +/* + * 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.ui.habits.list; + +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; + +import com.android.colorpicker.ColorPickerDialog; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.MainActivity; +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.ui.BaseScreen; +import org.isoron.uhabits.ui.about.AboutActivity; +import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; +import org.isoron.uhabits.ui.habits.edit.CreateHabitDialogFragment; +import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; +import org.isoron.uhabits.ui.intro.IntroActivity; +import org.isoron.uhabits.ui.settings.FilePickerDialog; +import org.isoron.uhabits.ui.settings.SettingsActivity; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.FileUtils; +import org.isoron.uhabits.utils.InterfaceUtils; + +import java.io.File; + +public class ListHabitsScreen extends BaseScreen +{ + @Nullable + ListHabitsController controller; + + @NonNull + private final ListHabitsRootView rootView; + + @NonNull + private final ListHabitsSelectionMenu selectionMenu; + + public ListHabitsScreen(@NonNull BaseActivity activity) + { + super(activity); + rootView = new ListHabitsRootView(activity); + setRootView(rootView); + + ListHabitsMenu menu = new ListHabitsMenu(activity, this); + selectionMenu = new ListHabitsSelectionMenu(this); + setMenu(menu); + setSelectionMenu(selectionMenu); + + HabitCardListAdapter adapter = new HabitCardListAdapter(); + rootView.setListAdapter(adapter); + selectionMenu.setAdapter(adapter); + } + + @Override + public void onResult(int requestCode, int resultCode, Intent data) + { + if (controller == null) return; + + switch (resultCode) + { + case HabitsApplication.RESULT_IMPORT_DATA: + showImportScreen(); + break; + + case HabitsApplication.RESULT_EXPORT_CSV: + controller.onExportCSV(); + break; + + case HabitsApplication.RESULT_EXPORT_DB: + controller.onExportDB(); + break; + + case HabitsApplication.RESULT_BUG_REPORT: + controller.onSendBugReport(); + break; + } + } + + public void setController(@Nullable ListHabitsController controller) + { + this.controller = controller; + rootView.setController(controller, selectionMenu); + } + + public void showAboutScreen() + { + Intent intent = new Intent(activity, AboutActivity.class); + activity.startActivity(intent); + } + + public void showColorPicker(Habit habit, OnColorSelectedListener callback) + { + int color = ColorUtils.getColor(activity, habit.color); + + ColorPickerDialog picker = + ColorPickerDialog.newInstance(R.string.color_picker_default_title, + ColorUtils.getPalette(activity), color, 4, + ColorPickerDialog.SIZE_SMALL); + + picker.setOnColorSelectedListener(c -> { + c = ColorUtils.colorToPaletteIndex(activity, c); + callback.onColorSelected(c); + }); + picker.show(activity.getSupportFragmentManager(), "picker"); + } + + public void showCreateHabitScreen() + { + showDialog(new CreateHabitDialogFragment(), "editHabit"); + } + + public void showDeleteConfirmationScreen(Callback callback) + { + new AlertDialog.Builder(activity) + .setTitle(R.string.delete_habits) + .setMessage(R.string.delete_habits_message) + .setPositiveButton(android.R.string.yes, + (dialog, which) -> callback.run()) + .setNegativeButton(android.R.string.no, null) + .show(); + } + + public void showEditHabitScreen(Habit habit) + { + BaseDialogFragment frag = + EditHabitDialogFragment.newInstance(habit.getId()); + frag.show(activity.getSupportFragmentManager(), "editHabit"); + } + + public void showFAQScreen() + { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(activity.getString(R.string.helpURL))); + activity.startActivity(intent); + } + + public void showHabitScreen(@NonNull Habit habit) + { + Intent intent = new Intent(activity, ShowHabitActivity.class); + intent.setData( + Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId())); + activity.startActivity(intent); + } + + public void showImportScreen() + { + if (controller == null) return; + + File dir = FileUtils.getFilesDir(null); + if (dir == null) + { + showMessage(R.string.could_not_import); + return; + } + + FilePickerDialog picker = new FilePickerDialog(activity, dir); + picker.setListener(file -> controller.onImportData(file)); + picker.show(); + } + + public void showIntroScreen() + { + Intent intent = new Intent(activity, IntroActivity.class); + activity.startActivity(intent); + } + + public void showSettingsScreen() + { + Intent intent = new Intent(activity, SettingsActivity.class); + activity.startActivityForResult(intent, 0); + } + + public void toggleNightMode() + { + if (InterfaceUtils.isNightMode()) + InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_LIGHT); + else InterfaceUtils.setCurrentTheme(InterfaceUtils.THEME_DARK); + + refreshTheme(); + } + + private void refreshTheme() + { + new Handler().postDelayed(() -> { + Intent intent = new Intent(activity, MainActivity.class); + + activity.finish(); + activity.overridePendingTransition(android.R.anim.fade_in, + android.R.anim.fade_out); + activity.startActivity(intent); + + }, 500); // HACK: Let the menu disappear first + } + + interface Callback + { + void run(); + } + + public interface OnColorSelectedListener + { + void onColorSelected(int color); + } + + @NonNull + public ListHabitsRootView getRootView() + { + return rootView; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java new file mode 100644 index 000000000..f38c2d21c --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java @@ -0,0 +1,200 @@ +/* + * 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.ui.habits.list; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.Menu; +import android.view.MenuItem; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.R; +import org.isoron.uhabits.commands.ArchiveHabitsCommand; +import org.isoron.uhabits.commands.ChangeHabitColorCommand; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.commands.DeleteHabitsCommand; +import org.isoron.uhabits.commands.UnarchiveHabitsCommand; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.BaseSelectionMenu; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; + +import java.util.List; + +import javax.inject.Inject; + +public class ListHabitsSelectionMenu extends BaseSelectionMenu + implements HabitCardListController.SelectionListener +{ + @NonNull + private final ListHabitsScreen screen; + + @Inject + CommandRunner commandRunner; + + @Nullable + private HabitCardListAdapter adapter; + + public ListHabitsSelectionMenu(@NonNull ListHabitsScreen screen) + { + this.screen = screen; + HabitsApplication.getComponent().inject(this); + } + + @Override + public void onDestroy() + { + if (adapter != null) adapter.clearSelection(); + super.onDestroy(); + } + + @Override + public boolean onItemClicked(@NonNull MenuItem item) + { + if (adapter == null) return false; + + List selected = adapter.getSelected(); + if (selected.isEmpty()) return false; + + Habit firstHabit = selected.get(0); + + switch (item.getItemId()) + { + case R.id.action_edit_habit: + edit(firstHabit); + finish(); + return true; + + case R.id.action_archive_habit: + archive(selected); + finish(); + return true; + + case R.id.action_unarchive_habit: + unarchive(selected); + finish(); + return true; + + case R.id.action_delete: + delete(selected); + return true; + + case R.id.action_color: + showColorPicker(selected, firstHabit); + return true; + + default: + return false; + } + } + + @Override + public boolean onPrepare(@NonNull Menu menu) + { + if (adapter == null) return false; + List selected = adapter.getSelected(); + + boolean showEdit = (selected.size() == 1); + boolean showArchive = true; + boolean showUnarchive = true; + for (Habit h : selected) + { + if (h.isArchived()) showArchive = false; + else showUnarchive = false; + } + + MenuItem itemEdit = menu.findItem(R.id.action_edit_habit); + MenuItem itemColor = menu.findItem(R.id.action_color); + MenuItem itemArchive = menu.findItem(R.id.action_archive_habit); + MenuItem itemUnarchive = menu.findItem(R.id.action_unarchive_habit); + + itemColor.setVisible(true); + itemEdit.setVisible(showEdit); + itemArchive.setVisible(showArchive); + itemUnarchive.setVisible(showUnarchive); + + setTitle(Integer.toString(selected.size())); + + return true; + } + + @Override + public void onSelectionChange() + { + invalidate(); + } + + @Override + public void onSelectionFinish() + { + finish(); + } + + @Override + public void onSelectionStart() + { + screen.startSelection(); + } + + public void setAdapter(@Nullable HabitCardListAdapter adapter) + { + if (adapter == null) return; + this.adapter = adapter; + } + + private void archive(@NonNull List selected) + { + commandRunner.execute(new ArchiveHabitsCommand(selected), null); + } + + private void delete(@NonNull List selected) + { + screen.showDeleteConfirmationScreen(() -> { + commandRunner.execute(new DeleteHabitsCommand(selected), null); + finish(); + }); + } + + private void edit(@NonNull Habit firstHabit) + { + screen.showEditHabitScreen(firstHabit); + } + + @Override + protected int getResourceId() + { + return R.menu.list_habits_selection; + } + + private void showColorPicker(@NonNull List selected, + @NonNull Habit firstHabit) + { + screen.showColorPicker(firstHabit, color -> { + commandRunner.execute(new ChangeHabitColorCommand(selected, color), + null); + finish(); + }); + } + + private void unarchive(@NonNull List selected) + { + commandRunner.execute(new UnarchiveHabitsCommand(selected), null); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/CheckmarkButtonController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/CheckmarkButtonController.java new file mode 100644 index 000000000..234d87f94 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/CheckmarkButtonController.java @@ -0,0 +1,98 @@ +/* + * 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.ui.habits.list.controllers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.views.CheckmarkButtonView; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Inject; + +public class CheckmarkButtonController +{ + @Nullable + private CheckmarkButtonView view; + + @Nullable + private Listener listener; + + @Inject + Preferences prefs; + + @NonNull + private Habit habit; + + private long timestamp; + + public CheckmarkButtonController(@NonNull Habit habit, long timestamp) + { + this.habit = habit; + this.timestamp = timestamp; + HabitsApplication.getComponent().inject(this); + } + + public void onClick() + { + if (prefs.isShortToggleEnabled()) performToggle(); + else performInvalidToggle(); + } + + public boolean onLongClick() + { + performToggle(); + return true; + } + + public void performInvalidToggle() + { + if (listener != null) listener.onInvalidToggle(); + } + + public void performToggle() + { + if (view != null) view.toggle(); + if (listener != null) listener.onToggle(habit, timestamp); + } + + public void setListener(@Nullable Listener listener) + { + this.listener = listener; + } + + public void setView(@Nullable CheckmarkButtonView view) + { + this.view = view; + } + + public interface Listener + { + /** + * Called when the user's attempt to perform a toggle is rejected. + */ + void onInvalidToggle(); + + + void onToggle(Habit habit, long timestamp); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java new file mode 100644 index 000000000..bdc65acf5 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java @@ -0,0 +1,61 @@ +/* + * 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.ui.habits.list.controllers; + +import android.support.annotation.Nullable; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.views.HabitCardView; + +public class HabitCardController implements HabitCardView.Controller +{ + @Nullable + private HabitCardView view; + + @Nullable + private Listener listener; + + @Override + public void onInvalidToggle() + { + if (listener != null) listener.onInvalidToggle(); + } + + @Override + public void onToggle(Habit habit, long timestamp) + { + if (view != null) view.triggerRipple(0, 0); + if (listener != null) listener.onToggle(habit, timestamp); + } + + public void setListener(@Nullable Listener listener) + { + this.listener = listener; + } + + public void setView(@Nullable HabitCardView view) + { + this.view = view; + } + + public interface Listener extends CheckmarkButtonController.Listener + { + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java new file mode 100644 index 000000000..2cfb03c98 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java @@ -0,0 +1,317 @@ +/* + * 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.ui.habits.list.controllers; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.mobeta.android.dslv.DragSortListView; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; + +/** + * Controller responsible for receiving and processing the events generated by a + * HabitListView. These include selecting and reordering items, toggling + * checkmarks and clicking habits. + */ +public class HabitCardListController implements DragSortListView.DropListener, + DragSortListView.DragListener, + HabitCardListView.Controller +{ + private final Mode NORMAL_MODE = new NormalMode(); + + private final Mode SELECTION_MODE = new SelectionMode(); + + @NonNull + private final HabitCardListAdapter adapter; + + @NonNull + private final HabitCardListView view; + + @Nullable + private HabitListener habitListener; + + @Nullable + private SelectionListener selectionListener; + + @NonNull + private Mode activeMode; + + public HabitCardListController(@NonNull HabitCardListAdapter adapter, + @NonNull HabitCardListView view) + { + this.adapter = adapter; + this.view = view; + this.activeMode = new NormalMode(); + } + + /** + * Called when the user is dragging a habit which was originally at position + * 'from' and is currently hovering over position 'to'. Note that the user + * has not yet finished the dragging operation. + * + * @param from the original position of the habit + * @param to the position where the habit is currently hovering + */ + @Override + public void drag(int from, int to) + { + // ignored + } + + /** + * Called when the user drags a habit and drops it somewhere. Note that the + * dragging operation is already complete. + * + * @param from the original position of the habit + * @param to the position where the habit was released + */ + @Override + public void drop(int from, int to) + { + if (from == to) return; + cancelSelection(); + + Habit habitFrom = adapter.getItem(from); + Habit habitTo = adapter.getItem(to); + adapter.reorder(from, to); + + if (habitListener != null) + habitListener.onHabitReorder(habitFrom, habitTo); + } + + /** + * Called when the user attempts to perform a toggle, but attempt is + * rejected. + */ + @Override + public void onInvalidToggle() + { + if (habitListener != null) habitListener.onInvalidToggle(); + } + + /** + * Called when the user clicks at some item. + * + * @param position the position of the clicked item + */ + @Override + public void onItemClick(int position) + { + activeMode.onItemClick(position); + } + + /** + * Called when the user long clicks at some item. + * + * @param position the position of the clicked item + */ + @Override + public void onItemLongClick(int position) + { + activeMode.onItemLongClick(position); + } + + /** + * Called when the user wants to toggle a checkmark. + * + * @param habit the habit of the checkmark + * @param timestamp the timestamps of the checkmark + */ + @Override + public void onToggle(Habit habit, long timestamp) + { + if (habitListener != null) habitListener.onToggle(habit, timestamp); + } + + public void setHabitListener(@Nullable HabitListener habitListener) + { + this.habitListener = habitListener; + } + + public void setSelectionListener(@Nullable SelectionListener listener) + { + this.selectionListener = listener; + } + + + /** + * Called when the user starts dragging an item. + * + * @param position the position of the habit dragged + */ + @Override + public void startDrag(int position) + { + activeMode.startDrag(position); + } + + /** + * Marks all items as not selected and finishes the selection operation. + */ + private void cancelSelection() + { + adapter.clearSelection(); + view.setDragEnabled(true); + activeMode = new NormalMode(); + + if (selectionListener != null) selectionListener.onSelectionFinish(); + } + + /** + * Selects or deselects the item at a given position + * + * @param position the position of the item to be selected/deselected + */ + protected void toggleSelection(int position) + { + adapter.toggleSelection(position); + activeMode = adapter.isSelectionEmpty() ? NORMAL_MODE : SELECTION_MODE; + } + + public interface HabitListener extends CheckmarkButtonController.Listener + { + /** + * Called when the user clicks a habit. + * + * @param habit the habit clicked + */ + void onHabitClick(Habit habit); + + /** + * Called when the user wants to change the position of a habit on the + * list. + * + * @param from habit to be moved + * @param to habit that currently occupies the desired position + */ + void onHabitReorder(Habit from, Habit to); + } + + /** + * A Mode describes the behaviour of the list upon clicking, long clicking + * and dragging an item. This depends on whether some items are already + * selected or not. + */ + private interface Mode + { + void onItemClick(int position); + + boolean onItemLongClick(int position); + + void startDrag(int position); + } + + public interface SelectionListener + { + /** + * Called when the user changes the list of selected item. This is only + * called if there were previously selected items. If the selection was + * previously empty, then onHabitSelectionStart is called instead. + */ + void onSelectionChange(); + + /** + * Called when the user deselects all items or cancels the selection. + */ + void onSelectionFinish(); + + /** + * Called after the user selects the first item. + */ + void onSelectionStart(); + } + + /** + * Mode activated when there are no items selected. Clicks trigger habit + * click. Long clicks start selection. + */ + class NormalMode implements Mode + { + @Override + public void onItemClick(int position) + { + Habit habit = adapter.getItem(position); + if (habitListener != null) habitListener.onHabitClick(habit); + } + + @Override + public boolean onItemLongClick(int position) + { + startSelection(position); + return true; + } + + @Override + public void startDrag(int position) + { + startSelection(position); + } + + protected void startSelection(int position) + { + toggleSelection(position); + activeMode = SELECTION_MODE; + if (selectionListener != null) selectionListener.onSelectionStart(); + } + } + + /** + * Mode activated when some items are already selected. + *

+ * Clicks toggle item selection. Long clicks select more items. + */ + class SelectionMode implements Mode + { + @Override + public void onItemClick(int position) + { + toggleSelection(position); + notifyListener(); + } + + @Override + public boolean onItemLongClick(int position) + { + toggleSelection(position); + notifyListener(); + return true; + } + + @Override + public void startDrag(int position) + { + toggleSelection(position); + notifyListener(); + } + + protected void notifyListener() + { + if (habitListener == null) return; + + if (activeMode == SELECTION_MODE) + selectionListener.onSelectionChange(); + else + selectionListener.onSelectionFinish(); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java new file mode 100644 index 000000000..d0650ac2d --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Contains controllers that are specific for ListHabitsActivity + */ +package org.isoron.uhabits.ui.habits.list.controllers; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java new file mode 100644 index 000000000..6aab91a27 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java @@ -0,0 +1,222 @@ +/* + * 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.ui.habits.list.model; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; +import org.isoron.uhabits.ui.habits.list.views.HabitCardView; + +import java.util.LinkedList; +import java.util.List; + +import javax.inject.Inject; + +/** + * Provides data that backs a {@link HabitCardListView}. The data if fetched and + * cached by a {@link HabitCardListCache}. This adapter also holds a list of + * items that have been selected. + */ +public class HabitCardListAdapter extends BaseAdapter + implements HabitCardListCache.Listener +{ + @NonNull + private ModelObservable observable; + + @Inject + @NonNull + HabitCardListCache cache; + + @Nullable + private HabitCardListView listView; + + @NonNull + private final LinkedList selected; + + public HabitCardListAdapter() + { + this.selected = new LinkedList<>(); + this.observable = new ModelObservable(); + + HabitsApplication.getComponent().inject(this); + + cache.setListener(this); + cache.setCheckmarkCount(5); // TODO: make this dynamic somehow + } + + /** + * Sets all items as not selected. + */ + public void clearSelection() + { + selected.clear(); + notifyDataSetChanged(); + } + + @Override + public int getCount() + { + return cache.getHabitCount(); + } + + public boolean getIncludeArchived() + { + return cache.getIncludeArchived(); + } + + /** + * Returns the item that occupies a certain position on the list + * + * @param position position of the item + * @return the item at given position + * @throws IndexOutOfBoundsException if position is not valid + */ + @Override + @NonNull + public Habit getItem(int position) + { + return cache.getHabitByPosition(position); + } + + @Override + public long getItemId(int position) + { + return getItem(position).getId(); + } + + @NonNull + public ModelObservable getObservable() + { + return observable; + } + + @NonNull + public List getSelected() + { + return new LinkedList<>(selected); + } + + @Override + public View getView(int position, + @Nullable View view, + @Nullable ViewGroup parent) + { + if (listView == null) return null; + + Habit habit = cache.getHabitByPosition(position); + int score = cache.getScore(habit.getId()); + int checkmarks[] = cache.getCheckmarks(habit.getId()); + boolean selected = this.selected.contains(habit); + + return listView.buildCardView((HabitCardView) view, habit, score, + checkmarks, selected); + } + + /** + * Returns whether list of selected items is empty. + * + * @return true if selection is empty, false otherwise + */ + public boolean isSelectionEmpty() + { + return selected.isEmpty(); + } + + /** + * Notify the adapter that it has been attached to a ListView. + */ + public void onAttached() + { + cache.onAttached(); + } + + @Override + public void onCacheRefresh() + { + notifyDataSetChanged(); + observable.notifyListeners(); + } + + /** + * Notify the adapter that it has been detached from a ListView. + */ + public void onDetached() + { + cache.onDetached(); + } + + /** + * Changes the order of habits on the adapter. + *

+ * Note that this only has effect on the adapter cache. The database is not + * modified, and the change is lost when the cache is refreshed. This method + * is useful for making the ListView more responsive: while we wait for the + * database operation to finish, the cache can be modified to reflect the + * changes immediately. + * + * @param from the habit that should be moved + * @param to the habit that currently occupies the desired position + */ + public void reorder(int from, int to) + { + cache.reorder(from, to); + cache.refreshAllHabits(false); + notifyDataSetChanged(); + } + + /** + * Sets the HabitCardListView that this adapter will provide data for. + *

+ * This object will be used to generated new HabitCardViews, upon demand. + * + * @param listView the HabitCardListView associated with this adapter + */ + public void setListView(@Nullable HabitCardListView listView) + { + this.listView = listView; + } + + /** + * Selects or deselects the item at a given position. + * + * @param position position of the item to be toggled + */ + public void toggleSelection(int position) + { + Habit h = getItem(position); + int k = selected.indexOf(h); + if (k < 0) selected.add(h); + else selected.remove(h); + notifyDataSetChanged(); + } + + public void setShowArchived(boolean showArchived) + { + cache.setIncludeArchived(showArchived); + cache.refreshAllHabits(true); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java new file mode 100644 index 000000000..0fb5711b1 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java @@ -0,0 +1,334 @@ +/* + * 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.ui.habits.list.model; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.commands.Command; +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import javax.inject.Inject; + +/** + * A HabitCardListCache fetches and keeps a cache of all the data necessary to + * render a HabitCardListView. + *

+ * This is needed since performing database lookups during scrolling can make + * the ListView very slow. It also registers itself as an observer of the + * models, in order to update itself automatically. + */ +public class HabitCardListCache implements CommandRunner.Listener +{ + boolean includeArchived; + + private int checkmarkCount; + + private BaseTask currentFetchTask; + + @Nullable + private Listener listener; + + @Nullable + private Long lastLoadTimestamp; + + @NonNull + private CacheData data; + + @Inject + CommandRunner commandRunner; + + public HabitCardListCache() + { + data = new CacheData(); + HabitsApplication.getComponent().inject(this); + } + + public int[] getCheckmarks(long habitId) + { + return data.checkmarks.get(habitId); + } + + /** + * Returns the habits that occupies a certain position on the list. + * + * @param position the position of the habit + * @return the habit at given position + * @throws IndexOutOfBoundsException if position is not valid + */ + @NonNull + public Habit getHabitByPosition(int position) + { + return data.habitsList.get(position); + } + + public int getHabitCount() + { + return data.habits.size(); + } + + @Nullable + public Long getLastLoadTimestamp() + { + return lastLoadTimestamp; + } + + public int getScore(long habitId) + { + return data.scores.get(habitId); + } + + public boolean getIncludeArchived() + { + return includeArchived; + } + + public void onAttached() + { +// refreshAllHabits(true); + if (lastLoadTimestamp == null) refreshAllHabits(true); + commandRunner.addListener(this); + } + + @Override + public void onCommandExecuted(@NonNull Command command, + @Nullable Long refreshKey) + { + if (refreshKey == null) refreshAllHabits(true); + else refreshHabit(refreshKey); + } + + public void onDetached() + { +// commandRunner.removeListener(this); + } + + public void refreshAllHabits(final boolean refreshScoresAndCheckmarks) + { + if (currentFetchTask != null) currentFetchTask.cancel(true); + currentFetchTask = new RefreshAllHabitsTask(refreshScoresAndCheckmarks); + currentFetchTask.execute(); + } + + public void refreshHabit(final Long id) + { + new RefreshHabitTask(id).execute(); + } + + public void reorder(int from, int to) + { + Habit fromHabit = data.habitsList.get(from); + Habit toHabit = data.habitsList.get(to); + + data.habitsList.remove(from); + data.habitsList.add(to, fromHabit); + + Habit.reorder(fromHabit, toHabit); + } + + public void setCheckmarkCount(int checkmarkCount) + { + this.checkmarkCount = checkmarkCount; + } + + public void setIncludeArchived(boolean includeArchived) + { + this.includeArchived = includeArchived; + } + + public void setListener(@Nullable Listener listener) + { + this.listener = listener; + } + + /** + * Interface definition for a callback to be invoked when the data on the + * cache has been modified. + */ + public interface Listener + { + /** + * Called when the data on the cache has been modified. + */ + void onCacheRefresh(); + } + + private class CacheData + { + @NonNull + public HashMap habits; + + @NonNull + public List habitsList; + + @NonNull + public HashMap checkmarks; + + @NonNull + public HashMap scores; + + /** + * Creates a new CacheData without any content. + */ + public CacheData() + { + habits = new HashMap<>(); + habitsList = new LinkedList<>(); + checkmarks = new HashMap<>(); + scores = new HashMap<>(); + } + + public void copyCheckmarksFrom(@NonNull CacheData oldData) + { + int[] empty = new int[checkmarkCount]; + + for (Long id : habits.keySet()) + { + if (oldData.checkmarks.containsKey(id)) + checkmarks.put(id, oldData.checkmarks.get(id)); + else checkmarks.put(id, empty); + } + } + + public void copyScoresFrom(@NonNull CacheData oldData) + { + for (Long id : habits.keySet()) + { + if (oldData.scores.containsKey(id)) + scores.put(id, oldData.scores.get(id)); + else scores.put(id, 0); + } + } + + public void fetchHabits() + { + habitsList = Habit.getAll(includeArchived); + for (Habit h : habitsList) + habits.put(h.getId(), h); + } + } + + private class RefreshAllHabitsTask extends BaseTask + { + @NonNull + private CacheData newData; + + private final boolean refreshScoresAndCheckmarks; + + public RefreshAllHabitsTask(boolean refreshScoresAndCheckmarks) + { + this.refreshScoresAndCheckmarks = refreshScoresAndCheckmarks; + newData = new CacheData(); + } + + private void commit() + { + data = newData; + } + + @Override + protected void doInBackground() + { + newData.fetchHabits(); + newData.copyScoresFrom(data); + newData.copyCheckmarksFrom(data); + + commit(); + + if (!refreshScoresAndCheckmarks) return; + + long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); + long dateFrom = + dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; + + int current = 0; + for (Habit h : newData.habitsList) + { + if (isCancelled()) return; + + Long id = h.getId(); + newData.scores.put(id, h.scores.getTodayValue()); + newData.checkmarks.put(id, + h.checkmarks.getValues(dateFrom, dateTo)); + + publishProgress(current++, newData.habits.size()); + } + } + + @Override + protected void onPostExecute(Void aVoid) + { + if (isCancelled()) return; + + lastLoadTimestamp = DateUtils.getStartOfToday(); + currentFetchTask = null; + + if (listener != null) listener.onCacheRefresh(); + super.onPostExecute(null); + } + + @Override + protected void onProgressUpdate(Integer... values) + { + if (listener != null) listener.onCacheRefresh(); + } + + } + + private class RefreshHabitTask extends BaseTask + { + private final Long id; + + public RefreshHabitTask(Long id) + { + this.id = id; + } + + @Override + protected void doInBackground() + { + long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); + long dateFrom = + dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; + + Habit h = Habit.get(id); + if (h == null) return; + + data.habits.put(id, h); + data.scores.put(id, h.scores.getTodayValue()); + data.checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo)); + } + + @Override + protected void onPostExecute(Void aVoid) + { + if (listener != null) listener.onCacheRefresh(); + super.onPostExecute(null); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HintList.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HintList.java new file mode 100644 index 000000000..096ae3a16 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HintList.java @@ -0,0 +1,81 @@ +/* + * 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.ui.habits.list.model; + +import android.support.annotation.NonNull; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Inject; + +/** + * Provides a list of hints to be shown at the application startup, and takes + * care of deciding when a new hint should be shown. + */ +public class HintList +{ + @Inject + Preferences prefs; + + @NonNull + private final String[] hints; + + /** + * Constructs a new list containing the provided hints. + * + * @param hints initial list of hints + */ + public HintList(@NonNull String hints[]) + { + this.hints = hints; + HabitsApplication.getComponent().inject(this); + } + + /** + * Returns a new hint to be shown to the user. + *

+ * The hint returned is marked as read on the list, and will not be returned + * again. In case all hints have already been read, and there is nothing + * left, returns null. + * + * @return the next hint to be shown, or null if none + */ + public String pop() + { + int next = prefs.getLastHintNumber() + 1; + if (next >= hints.length) return null; + + prefs.updateLastHint(next, DateUtils.getStartOfToday()); + return hints[next]; + } + + /** + * Returns whether it is time to show a new hint or not. + * + * @return true if hint should be shown, false otherwise + */ + public boolean shouldShow() + { + long lastHintTimestamp = prefs.getLastHintTimestamp(); + return (DateUtils.getStartOfToday() > lastHintTimestamp); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java new file mode 100644 index 000000000..06a063daf --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Contains model classes that are specific for ListHabitsActivity + */ +package org.isoron.uhabits.ui.habits.list.model; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java new file mode 100644 index 000000000..5ae316016 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Contains classes for ListHabitsActivity. + */ +package org.isoron.uhabits.ui.habits.list; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java new file mode 100644 index 000000000..2fcd34406 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java @@ -0,0 +1,116 @@ +/* + * 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.ui.habits.list.views; + +import android.content.Context; +import android.graphics.Canvas; +import android.view.HapticFeedbackConstants; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.isoron.uhabits.utils.InterfaceUtils; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class CheckmarkButtonView extends FrameLayout +{ + private int color; + + private int value; + + @BindView(R.id.tvCheck) + TextView tvCheck; + + public CheckmarkButtonView(Context context) + { + super(context); + init(); + } + + public void setColor(int color) + { + this.color = color; + postInvalidate(); + } + + public void setController(final CheckmarkButtonController controller) + { + setOnClickListener(v -> controller.onClick()); + setOnLongClickListener(v -> controller.onLongClick()); + } + + public void setValue(int value) + { + this.value = value; + postInvalidate(); + } + + public void toggle() + { + value = (value == Checkmark.CHECKED_EXPLICITLY ? Checkmark.UNCHECKED : + Checkmark.CHECKED_EXPLICITLY); + + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + postInvalidate(); + } + + private void init() + { + addView( + inflate(getContext(), R.layout.list_habits_card_checkmark, null)); + ButterKnife.bind(this); + + setWillNotDraw(false); + setHapticFeedbackEnabled(false); + + tvCheck.setTypeface(InterfaceUtils.getFontAwesome(getContext())); + } + + @Override + protected void onDraw(Canvas canvas) + { + int lowContrastColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + + if (value == Checkmark.CHECKED_EXPLICITLY) + { + tvCheck.setText(R.string.fa_check); + tvCheck.setTextColor(color); + } + + if (value == Checkmark.CHECKED_IMPLICITLY) + { + tvCheck.setText(R.string.fa_check); + tvCheck.setTextColor(lowContrastColor); + } + + if (value == Checkmark.UNCHECKED) + { + tvCheck.setText(R.string.fa_times); + tvCheck.setTextColor(lowContrastColor); + } + + super.onDraw(canvas); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelView.java new file mode 100644 index 000000000..93e22b127 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelView.java @@ -0,0 +1,190 @@ +/* + * 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.ui.habits.list.views; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Inject; + +public class CheckmarkPanelView extends LinearLayout +{ + private static final int CHECKMARK_LEFT_TO_RIGHT = 0; + + private static final int CHECKMARK_RIGHT_TO_LEFT = 1; + + @Inject + Preferences prefs; + + private int checkmarkValues[]; + + private int nButtons; + + private int color; + + private Controller controller; + + @NonNull + private Habit habit; + + public CheckmarkPanelView(Context context) + { + super(context); + init(); + } + + public CheckmarkPanelView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + public CheckmarkPanelView(Context context, + AttributeSet attrs, + int defStyleAttr) + { + super(context, attrs, defStyleAttr); + init(); + } + + public CheckmarkButtonView getButton(int position) + { + return (CheckmarkButtonView) getChildAt(position); + } + + public void setCheckmarkValues(int[] checkmarkValues) + { + this.checkmarkValues = checkmarkValues; + + if (this.nButtons != checkmarkValues.length) + { + this.nButtons = checkmarkValues.length; + addCheckmarkButtons(); + } + + setupCheckmarkButtons(); + } + + public void setColor(int color) + { + this.color = color; + setupCheckmarkButtons(); + } + + public void setController(Controller controller) + { + this.controller = controller; + } + + public void setHabit(@NonNull Habit habit) + { + this.habit = habit; + } + + private void addCheckmarkButtons() + { + removeAllViews(); + + for (int i = 0; i < nButtons; i++) + addView(new CheckmarkButtonView(getContext())); + } + + private int getCheckmarkOrder() + { + if (isInEditMode()) return CHECKMARK_LEFT_TO_RIGHT; + return prefs.shouldReverseCheckmarks() ? CHECKMARK_RIGHT_TO_LEFT : + CHECKMARK_LEFT_TO_RIGHT; + } + + private CheckmarkButtonView indexToButton(int i) + { + int position = i; + + if (getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT) + position = nButtons - i - 1; + + return (CheckmarkButtonView) getChildAt(position); + } + + private void init() + { + if (isInEditMode()) return; + HabitsApplication.getComponent().inject(this); + setWillNotDraw(false); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth); + float buttonHeight = + getResources().getDimension(R.dimen.checkmarkHeight); + + float width = buttonWidth * nButtons; + + widthMeasureSpec = + MeasureSpec.makeMeasureSpec((int) width, MeasureSpec.EXACTLY); + heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) buttonHeight, + MeasureSpec.EXACTLY); + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private void setupButtonControllers(long timestamp, + CheckmarkButtonView buttonView) + { + if (controller == null) return; + + CheckmarkButtonController buttonController = + new CheckmarkButtonController(habit, timestamp); + + buttonController.setListener(controller); + buttonController.setView(buttonView); + buttonView.setController(buttonController); + } + + private void setupCheckmarkButtons() + { + long timestamp = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; + + for (int i = 0; i < nButtons; i++) + { + CheckmarkButtonView buttonView = indexToButton(i); + buttonView.setValue(checkmarkValues[i]); + buttonView.setColor(color); + setupButtonControllers(timestamp, buttonView); + timestamp -= day; + } + } + + public interface Controller extends CheckmarkButtonController.Listener + { + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java new file mode 100644 index 000000000..e1d0832a3 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java @@ -0,0 +1,165 @@ +/* + * 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.ui.habits.list.views; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ListAdapter; + +import com.mobeta.android.dslv.DragSortController; +import com.mobeta.android.dslv.DragSortListView; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardController; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; + +public class HabitCardListView extends DragSortListView +{ + @Nullable + private HabitCardListAdapter adapter; + + @Nullable + private Controller controller; + + public HabitCardListView(Context context, AttributeSet attrs) + { + super(context, attrs); + setFloatViewManager(new ViewManager()); + setDragEnabled(true); + setLongClickable(true); + } + + /** + * Builds a new HabitCardView to be eventually added to this list, + * containing the given data. + * + * @param cardView an old HabitCardView that should be reused if possible, + * possibly null + * @param habit the habit for this card + * @param score the current score for the habit + * @param checkmarks the list of checkmark values to be included in the + * card + * @param selected true if the card is selected, false otherwise + * @return the HabitCardView generated + */ + public View buildCardView(@Nullable HabitCardView cardView, + @NonNull Habit habit, + int score, + int[] checkmarks, + boolean selected) + { + if (cardView == null) cardView = new HabitCardView(getContext()); + + cardView.setHabit(habit); + cardView.setSelected(selected); + cardView.setCheckmarkValues(checkmarks); + cardView.setScore(score); + + if (controller != null) + { + HabitCardController cardController = new HabitCardController(); + cardController.setListener(controller); + cardView.setController(cardController); + cardController.setView(cardView); + } + + return cardView; + } + + @Override + public void setAdapter(ListAdapter adapter) + { + this.adapter = (HabitCardListAdapter) adapter; + super.setAdapter(adapter); + } + + public void setController(@Nullable Controller controller) + { + this.controller = controller; + setDropListener(controller); + setDragListener(controller); + setOnItemClickListener(null); + setOnLongClickListener(null); + + if (controller == null) return; + + setOnItemClickListener((p, v, pos, id) -> controller.onItemClick(pos)); + setOnItemLongClickListener((p, v, pos, id) -> { + controller.onItemLongClick(pos); + return true; + }); + } + + public void toggleShowArchived() + { +// showArchived = !showArchived; +// cache.setIncludeArchived(showArchived); +// cache.refreshAllHabits(true); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + if (adapter != null) adapter.onAttached(); + } + + @Override + protected void onDetachedFromWindow() + { + if (adapter != null) adapter.onDetached(); + super.onDetachedFromWindow(); + } + + public interface Controller extends CheckmarkButtonController.Listener, + HabitCardController.Listener, + DropListener, + DragListener + { + void onItemClick(int pos); + + void onItemLongClick(int pos); + } + + private class ViewManager extends DragSortController + { + public ViewManager() + { + super(HabitCardListView.this); + setRemoveEnabled(false); + } + + @Override + public View onCreateFloatView(int position) + { + if (adapter == null) return null; + return adapter.getView(position, null, null); + } + + @Override + public void onDestroyFloatView(View floatView) + { + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java new file mode 100644 index 000000000..e8e758929 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java @@ -0,0 +1,216 @@ +/* + * 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.ui.habits.list.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.views.RingView; + +import java.util.Random; + +import butterknife.BindView; +import butterknife.ButterKnife; + +import static org.isoron.uhabits.utils.InterfaceUtils.getStyledColor; +import static org.isoron.uhabits.utils.InterfaceUtils.getStyledDrawable; + +public class HabitCardView extends FrameLayout +{ + private Habit habit; + + @BindView(R.id.checkmarkPanel) + CheckmarkPanelView checkmarkPanel; + + @BindView(R.id.innerFrame) + LinearLayout innerFrame; + + @BindView(R.id.label) + TextView label; + + @BindView(R.id.scoreRing) + RingView scoreRing; + + private final Context context = getContext(); + + public HabitCardView(Context context) + { + super(context); + init(); + } + + public HabitCardView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + public HabitCardView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + init(); + } + + public void setCheckmarkValues(int checkmarks[]) + { + checkmarkPanel.setCheckmarkValues(checkmarks); + postInvalidate(); + } + + public void setController(Controller controller) + { + checkmarkPanel.setController(null); + if (controller == null) return; + + checkmarkPanel.setController(controller); + } + + public void setHabit(Habit habit) + { + this.habit = habit; + int color = getActiveColor(habit); + + label.setText(habit.name); + label.setTextColor(color); + scoreRing.setColor(color); + checkmarkPanel.setColor(color); + checkmarkPanel.setHabit(habit); + + postInvalidate(); + } + + public void setScore(int score) + { + float percentage = (float) score / Score.MAX_VALUE; + scoreRing.setPercentage(percentage); + scoreRing.setPrecision(1.0f / 16); + postInvalidate(); + } + + @Override + public void setSelected(boolean isSelected) + { + super.setSelected(isSelected); + updateBackground(isSelected); + } + + public void triggerRipple(final float x, final float y) + { + final Drawable background = innerFrame.getBackground(); + if (android.os.Build.VERSION.SDK_INT >= 21) background.setHotspot(x, y); + background.setState(new int[]{ + android.R.attr.state_pressed, android.R.attr.state_enabled + }); + new Handler().postDelayed(() -> background.setState(new int[]{}), 25); + } + + private int getActiveColor(Habit habit) + { + int mediumContrastColor = + getStyledColor(context, R.attr.mediumContrastTextColor); + int activeColor = ColorUtils.getColor(context, habit.color); + if (habit.isArchived()) activeColor = mediumContrastColor; + + return activeColor; + } + + private void init() + { + setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + inflate(context, R.layout.list_habits_card, this); + ButterKnife.bind(this); + + innerFrame.setOnTouchListener((v, event) -> { + if (android.os.Build.VERSION.SDK_INT >= 21) + v.getBackground().setHotspot(event.getX(), event.getY()); + return false; + }); + + if (isInEditMode()) initEditMode(); + } + + @SuppressLint("SetTextI18n") + private void initEditMode() + { + String habits[] = { + "Wake up early", + "Wash dishes", + "Exercise", + "Meditate", + "Play guitar", + "Wash clothes", + "Get a haircut" + }; + + Random rand = new Random(); + int color = ColorUtils.CSV_PALETTE[rand.nextInt(10)]; + int[] values = { + rand.nextInt(3), + rand.nextInt(3), + rand.nextInt(3), + rand.nextInt(3), + rand.nextInt(3) + }; + + label.setText(habits[rand.nextInt(habits.length)]); + label.setTextColor(color); + scoreRing.setColor(color); + scoreRing.setPercentage(rand.nextFloat()); + checkmarkPanel.setColor(color); + checkmarkPanel.setCheckmarkValues(values); + } + + private void updateBackground(boolean isSelected) + { + if (android.os.Build.VERSION.SDK_INT >= 21) + { + if (isSelected) + innerFrame.setBackgroundResource(R.drawable.selected_box); + else innerFrame.setBackgroundResource(R.drawable.ripple); + } + else + { + Drawable background; + + if (isSelected) background = + getStyledDrawable(context, R.attr.selectedBackground); + else background = getStyledDrawable(context, R.attr.cardBackground); + + innerFrame.setBackgroundDrawable(background); + } + } + + public interface Controller extends CheckmarkPanelView.Controller + { + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java new file mode 100644 index 000000000..17a361087 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java @@ -0,0 +1,94 @@ +/* + * 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.ui.habits.list.views; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.GregorianCalendar; + +public class HeaderView extends LinearLayout +{ + private static final int CHECKMARK_LEFT_TO_RIGHT = 0; + + private static final int CHECKMARK_RIGHT_TO_LEFT = 1; + + private final Context context; + + public HeaderView(Context context, AttributeSet attrs) + { + super(context, attrs); + this.context = context; + } + + private void createButtons() + { + removeAllViews(); + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); + + for (int i = 0; i < getButtonCount(); i++) + { + int position = 0; + + if (getCheckmarkOrder() == CHECKMARK_LEFT_TO_RIGHT) position = i; + + View tvDay = + inflate(context, R.layout.list_habits_header_checkmark, null); + TextView btCheck = (TextView) tvDay.findViewById(R.id.tvCheck); + btCheck.setText(DateUtils.formatHeaderDate(day)); + addView(tvDay, position); + day.add(GregorianCalendar.DAY_OF_MONTH, -1); + } + } + + private int getButtonCount() + { + float labelWidth = getResources().getDimension(R.dimen.habitNameWidth); + float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth); + return Math.max(0, + (int) ((getMeasuredWidth() - labelWidth) / buttonWidth)); + } + + private int getCheckmarkOrder() + { + if (isInEditMode()) return CHECKMARK_LEFT_TO_RIGHT; + + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(getContext()); + boolean reverse = + prefs.getBoolean("pref_checkmark_reverse_order", false); + return reverse ? CHECKMARK_RIGHT_TO_LEFT : CHECKMARK_LEFT_TO_RIGHT; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + createButtons(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HintView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HintView.java new file mode 100644 index 000000000..5fa6ba7f6 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HintView.java @@ -0,0 +1,138 @@ +/* + * 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.ui.habits.list.views; + +import android.animation.AnimatorListenerAdapter; +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.isoron.uhabits.R; +import org.isoron.uhabits.ui.habits.list.model.HintList; + +import java.util.Random; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class HintView extends FrameLayout +{ + @BindView(R.id.hintContent) + TextView hintContent; + + @Nullable + private HintList hintList; + + public HintView(Context context) + { + super(context); + init(); + } + + public HintView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + public HintView(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + init(); + } + + @Override + public void onAttachedToWindow() + { + super.onAttachedToWindow(); + showNext(); + } + + /** + * Sets the list of hints to be shown + * + * @param hintList the list of hints to be shown + */ + public void setHints(@Nullable HintList hintList) + { + this.hintList = hintList; + } + + private void dismiss() + { + animate().alpha(0f).setDuration(500).setListener(new DismissAnimator()); + } + + private void init() + { + addView(inflate(getContext(), R.layout.list_habits_hint, null)); + ButterKnife.bind(this); + + setVisibility(GONE); + setClickable(true); + setOnClickListener(v -> dismiss()); + + if (isInEditMode()) initEditMode(); + } + + @SuppressLint("SetTextI18n") + private void initEditMode() + { + String hints[] = { + "Cats are the most popular pet in the United States: There " + + "are 88 million pet cats and 74 million dogs.", + "A cat has been mayor of Talkeetna, Alaska, for 15 years. " + + "His name is Stubbs.", + "Cats can’t taste sweetness." + }; + + int k = new Random().nextInt(hints.length); + hintContent.setText(hints[k]); + setVisibility(VISIBLE); + setAlpha(1.0f); + } + + private void showNext() + { + if (hintList == null) return; + if (!hintList.shouldShow()) return; + + String hint = hintList.pop(); + if (hint == null) return; + + hintContent.setText(hint); + setAlpha(0.0f); + setVisibility(View.VISIBLE); + animate().alpha(1f).setDuration(500); + } + + private class DismissAnimator extends AnimatorListenerAdapter + { + @Override + public void onAnimationEnd(android.animation.Animator animation) + { + setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java index 1d15e642e..929e801c5 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java @@ -25,10 +25,13 @@ import android.os.Bundle; import android.support.v7.app.ActionBar; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.BaseActivity; +/** + * Activity that allows the user to see more information about a single habit. + * Shows all the metadata for the habit, in addition to several charts. + */ public class ShowHabitActivity extends BaseActivity { private Habit habit; @@ -42,19 +45,19 @@ public class ShowHabitActivity extends BaseActivity habit = Habit.get(ContentUris.parseId(data)); setContentView(R.layout.show_habit_activity); - setupSupportActionBar(true); +// setupSupportActionBar(true); setupHabitActionBar(); } public void setupHabitActionBar() { - if(habit == null) return; + if (habit == null) return; ActionBar actionBar = getSupportActionBar(); - if(actionBar == null) return; + if (actionBar == null) return; actionBar.setTitle(habit.name); - setupActionBarColor(ColorUtils.getColor(this, habit.color)); +// setupActionBarColor(ColorUtils.getColor(this, habit.color)); } public Habit getHabit() diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index f088b0d33..3ba99c833 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -52,29 +52,47 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -public class ShowHabitFragment extends Fragment implements ModelObservable.Listener +public class ShowHabitFragment extends Fragment + implements ModelObservable.Listener { Habit habit; float todayScore; + float lastMonthScore; + float lastYearScore; + int activeColor; + int inactiveColor; + int previousScoreInterval; private ShowHabitHelper helper; + protected ShowHabitActivity activity; + private List dataViews; - @BindView(R.id.sStrengthInterval) Spinner sStrengthInterval; - @BindView(R.id.scoreView) HabitScoreView habitScoreView; - @BindView(R.id.historyView) HabitHistoryView habitHistoryView; - @BindView(R.id.punchcardView) HabitFrequencyView habitFrequencyView; - @BindView(R.id.streakView) HabitStreakView habitStreakView; + @BindView(R.id.sStrengthInterval) + Spinner sStrengthInterval; + + @BindView(R.id.scoreView) + HabitScoreView habitScoreView; + + @BindView(R.id.historyView) + HabitHistoryView habitHistoryView; + + @BindView(R.id.punchcardView) + HabitFrequencyView habitFrequencyView; + + @BindView(R.id.streakView) + HabitStreakView habitStreakView; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, + public View onCreateView(LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.show_habit, container, false); @@ -87,12 +105,14 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste helper.updateColors(); helper.updateMainHeader(view); - int defaultScoreInterval = InterfaceUtils.getDefaultScoreInterval(getContext()); + int defaultScoreInterval = + InterfaceUtils.getDefaultScoreInterval(getContext()); previousScoreInterval = defaultScoreInterval; setScoreBucketSize(defaultScoreInterval); sStrengthInterval.setSelection(defaultScoreInterval); - sStrengthInterval.setOnItemSelectedListener(new OnItemSelectedListener()); + sStrengthInterval.setOnItemSelectedListener( + new OnItemSelectedListener()); createDataViews(); helper.updateCardHeaders(view); @@ -117,7 +137,7 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste dataViews.add(habitFrequencyView); dataViews.add(habitStreakView); - for(HabitDataView dataView : dataViews) + for (HabitDataView dataView : dataViews) dataView.setHabit(habit); } @@ -131,7 +151,7 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.show_habit_fragment, menu); +// inflater.inflate(R.menu.show_habit_fragment, menu); } @Override @@ -145,9 +165,10 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste private boolean showEditHabitDialog() { - if(habit == null) return false; + if (habit == null) return false; - BaseDialogFragment frag = EditHabitDialogFragment.newInstance(habit.getId()); + BaseDialogFragment frag = + EditHabitDialogFragment.newInstance(habit.getId()); frag.show(getFragmentManager(), "editHabit"); return true; } @@ -159,12 +180,12 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste private void setScoreBucketSize(int position) { - if(getView() == null) return; + if (getView() == null) return; - habitScoreView.setBucketSize(HabitScoreView.DEFAULT_BUCKET_SIZES[position]); + habitScoreView.setBucketSize( + HabitScoreView.DEFAULT_BUCKET_SIZES[position]); - if(position != previousScoreInterval) - refreshData(); + if (position != previousScoreInterval) refreshData(); InterfaceUtils.setDefaultScoreInterval(getContext(), position); previousScoreInterval = position; @@ -182,7 +203,7 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste helper.updateColors(); helper.updateMainHeader(getView()); helper.updateCardHeaders(getView()); - if(activity != null) activity.setupHabitActionBar(); + if (activity != null) activity.setupHabitActionBar(); } }); } @@ -206,8 +227,8 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste @Override protected void doInBackground() { - if(habit == null) return; - if(dataViews == null) return; + if (habit == null) return; + if (dataViews == null) return; long today = DateUtils.getStartOfToday(); long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; @@ -226,10 +247,14 @@ public class ShowHabitFragment extends Fragment implements ModelObservable.Liste } } - private class OnItemSelectedListener implements AdapterView.OnItemSelectedListener + private class OnItemSelectedListener + implements AdapterView.OnItemSelectedListener { @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) + public void onItemSelected(AdapterView parent, + View view, + int position, + long id) { setScoreBucketSize(position); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java index 026327d22..6a76d4f32 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -41,24 +41,26 @@ public class ShowHabitHelper String getFreqText() { - if(fragment.habit == null) return ""; + if (fragment.habit == null) return ""; Resources resources = fragment.getResources(); Integer freqNum = fragment.habit.freqNum; Integer freqDen = fragment.habit.freqDen; - if (freqNum.equals(freqDen)) return resources.getString(R.string.every_day); + if (freqNum.equals(freqDen)) + return resources.getString(R.string.every_day); if (freqNum == 1) { if (freqDen == 7) return resources.getString(R.string.every_week); - if (freqDen % 7 == 0) return resources.getString(R.string.every_x_weeks, freqDen / 7); + if (freqDen % 7 == 0) + return resources.getString(R.string.every_x_weeks, freqDen / 7); return resources.getString(R.string.every_x_days, freqDen); } String times_every = resources.getString(R.string.time_every); return String.format("%d %s %d %s", freqNum, times_every, freqDen, - resources.getString(R.string.days)); + resources.getString(R.string.days)); } void updateScore(View view) @@ -67,48 +69,62 @@ public class ShowHabitHelper if (view == null) return; float todayPercentage = fragment.todayScore / Score.MAX_VALUE; - float monthDiff = todayPercentage - (fragment.lastMonthScore / Score.MAX_VALUE); - float yearDiff = todayPercentage - (fragment.lastYearScore / Score.MAX_VALUE); + float monthDiff = + todayPercentage - (fragment.lastMonthScore / Score.MAX_VALUE); + float yearDiff = + todayPercentage - (fragment.lastYearScore / Score.MAX_VALUE); RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); - int androidColor = ColorUtils.getColor(fragment.getActivity(), fragment.habit.color); + int androidColor = + ColorUtils.getColor(fragment.getActivity(), fragment.habit.color); scoreRing.setColor(androidColor); scoreRing.setPercentage(todayPercentage); TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel); - TextView monthDiffLabel = (TextView) view.findViewById(R.id.monthDiffLabel); - TextView yearDiffLabel = (TextView) view.findViewById(R.id.yearDiffLabel); + TextView monthDiffLabel = + (TextView) view.findViewById(R.id.monthDiffLabel); + TextView yearDiffLabel = + (TextView) view.findViewById(R.id.yearDiffLabel); scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); String minus = "\u2212"; - monthDiffLabel.setText(String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), + monthDiffLabel.setText( + String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), Math.abs(monthDiff) * 100)); yearDiffLabel.setText( - String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), Math.abs(yearDiff) * 100)); + String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), + Math.abs(yearDiff) * 100)); - monthDiffLabel.setTextColor(monthDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); - yearDiffLabel.setTextColor(yearDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); + monthDiffLabel.setTextColor( + monthDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); + yearDiffLabel.setTextColor( + yearDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); } void updateMainHeader(View view) { if (fragment.habit == null) return; - TextView questionLabel = (TextView) view.findViewById(R.id.questionLabel); + TextView questionLabel = + (TextView) view.findViewById(R.id.questionLabel); questionLabel.setTextColor(fragment.activeColor); questionLabel.setText(fragment.habit.description); - TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel); + TextView reminderLabel = + (TextView) view.findViewById(R.id.reminderLabel); if (fragment.habit.hasReminder()) reminderLabel.setText( - DateUtils.formatTime(fragment.getActivity(), fragment.habit.reminderHour, - fragment.habit.reminderMin)); - else reminderLabel.setText(fragment.getResources().getString(R.string.reminder_off)); + DateUtils.formatTime(fragment.getActivity(), + fragment.habit.reminderHour, fragment.habit.reminderMin)); + else reminderLabel.setText( + fragment.getResources().getString(R.string.reminder_off)); - TextView frequencyLabel = (TextView) view.findViewById(R.id.frequencyLabel); + TextView frequencyLabel = + (TextView) view.findViewById(R.id.frequencyLabel); frequencyLabel.setText(getFreqText()); - if (fragment.habit.description.isEmpty()) questionLabel.setVisibility(View.GONE); + if (fragment.habit.description.isEmpty()) + questionLabel.setVisibility(View.GONE); } void updateCardHeaders(View view) @@ -123,17 +139,20 @@ public class ShowHabitHelper void updateColor(View view, int viewId) { - if(fragment.habit == null || fragment.activity == null) return; + if (fragment.habit == null || fragment.activity == null) return; TextView textView = (TextView) view.findViewById(viewId); - int androidColor = ColorUtils.getColor(fragment.activity, fragment.habit.color); + int androidColor = + ColorUtils.getColor(fragment.activity, fragment.habit.color); textView.setTextColor(androidColor); } void updateColors() { - fragment.activeColor = ColorUtils.getColor(fragment.getContext(), fragment.habit.color); - fragment.inactiveColor = InterfaceUtils.getStyledColor(fragment.getContext(), + fragment.activeColor = + ColorUtils.getColor(fragment.getContext(), fragment.habit.color); + fragment.inactiveColor = + InterfaceUtils.getStyledColor(fragment.getContext(), R.attr.mediumContrastTextColor); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java b/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java index 1b0229633..0e6bcdf45 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/intro/IntroActivity.java @@ -27,6 +27,10 @@ import com.github.paolorotolo.appintro.AppIntroFragment; import org.isoron.uhabits.R; +/** + * Activity that introduces the app to the user, shown only after the app is + * launched for the first time. + */ public class IntroActivity extends AppIntro2 { @Override @@ -35,16 +39,16 @@ public class IntroActivity extends AppIntro2 showStatusBar(false); addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_1), - getString(R.string.intro_description_1), R.drawable.intro_icon_1, - Color.parseColor("#194673"))); + getString(R.string.intro_description_1), R.drawable.intro_icon_1, + Color.parseColor("#194673"))); addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_2), - getString(R.string.intro_description_2), R.drawable.intro_icon_2, - Color.parseColor("#ffa726"))); + getString(R.string.intro_description_2), R.drawable.intro_icon_2, + Color.parseColor("#ffa726"))); addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4), - getString(R.string.intro_description_4), R.drawable.intro_icon_4, - Color.parseColor("#9575cd"))); + getString(R.string.intro_description_4), R.drawable.intro_icon_4, + Color.parseColor("#9575cd"))); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java new file mode 100644 index 000000000..68eee2b1e --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Contains classes for the IntroActivity. + */ +package org.isoron.uhabits.ui.intro; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java b/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java index f31fad843..8d7faa1c7 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/FilePickerDialog.java @@ -39,8 +39,11 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener private static final String PARENT_DIR = ".."; private final Activity activity; + private ListView list; + private Dialog dialog; + private File currentPath; public interface OnFileSelectedListener @@ -59,21 +62,24 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener dialog = new Dialog(activity); dialog.setContentView(list); - dialog.getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + dialog + .getWindow() + .setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); navigateTo(initialDirectory); } @Override - public void onItemClick(AdapterView parent, View view, int which, long id) + public void onItemClick(AdapterView parent, + View view, + int which, + long id) { String filename = (String) list.getItemAtPosition(which); File file; - if (filename.equals(PARENT_DIR)) - file = currentPath.getParentFile(); - else - file = new File(currentPath, filename); + if (filename.equals(PARENT_DIR)) file = currentPath.getParentFile(); + else file = new File(currentPath, filename); if (file.isDirectory()) { @@ -102,7 +108,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener File[] dirs = path.listFiles(new ReadableDirFilter()); File[] files = path.listFiles(new RegularReadableFileFilter()); - if(dirs == null || files == null) return; + if (dirs == null || files == null) return; this.currentPath = path; dialog.setTitle(currentPath.getPath()); @@ -142,7 +148,8 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener { public FilePickerAdapter(@NonNull String[] fileList) { - super(FilePickerDialog.this.activity, android.R.layout.simple_list_item_1, fileList); + super(FilePickerDialog.this.activity, + android.R.layout.simple_list_item_1, fileList); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java index d9188b5c9..aebebc8aa 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java @@ -22,9 +22,12 @@ package org.isoron.uhabits.ui.settings; import android.os.Bundle; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.utils.InterfaceUtils; +/** + * Activity that allows the user to view and modify the app settings. + */ public class SettingsActivity extends BaseActivity { @Override @@ -32,9 +35,10 @@ public class SettingsActivity extends BaseActivity { super.onCreate(savedInstanceState); setContentView(R.layout.settings_activity); - setupSupportActionBar(true); +// setupSupportActionBar(true); - int color = InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); - setupActionBarColor(color); + int color = + InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); +// setupActionBarColor(color); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java index bdb925c12..93aa9af80 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java @@ -29,11 +29,11 @@ import android.support.v7.preference.PreferenceFragmentCompat; import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ReminderUtils; import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.utils.ReminderUtils; public class SettingsFragment extends PreferenceFragmentCompat - implements SharedPreferences.OnSharedPreferenceChangeListener + implements SharedPreferences.OnSharedPreferenceChangeListener { private static int RINGTONE_REQUEST_CODE = 1; @@ -43,14 +43,18 @@ public class SettingsFragment extends PreferenceFragmentCompat super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); - setResultOnPreferenceClick("importData", HabitsApplication.RESULT_IMPORT_DATA); - setResultOnPreferenceClick("exportCSV", HabitsApplication.RESULT_EXPORT_CSV); - setResultOnPreferenceClick("exportDB", HabitsApplication.RESULT_EXPORT_DB); - setResultOnPreferenceClick("bugReport", HabitsApplication.RESULT_BUG_REPORT); + setResultOnPreferenceClick("importData", + HabitsApplication.RESULT_IMPORT_DATA); + setResultOnPreferenceClick("exportCSV", + HabitsApplication.RESULT_EXPORT_CSV); + setResultOnPreferenceClick("exportDB", + HabitsApplication.RESULT_EXPORT_DB); + setResultOnPreferenceClick("bugReport", + HabitsApplication.RESULT_BUG_REPORT); updateRingtoneDescription(); - if(InterfaceUtils.isLocaleFullyTranslated()) + if (InterfaceUtils.isLocaleFullyTranslated()) removePreference("translate", "linksCategory"); } @@ -62,7 +66,8 @@ public class SettingsFragment extends PreferenceFragmentCompat private void removePreference(String preferenceKey, String categoryKey) { - PreferenceCategory cat = (PreferenceCategory) findPreference(categoryKey); + PreferenceCategory cat = + (PreferenceCategory) findPreference(categoryKey); Preference pref = findPreference(preferenceKey); cat.removePreference(pref); } @@ -70,16 +75,17 @@ public class SettingsFragment extends PreferenceFragmentCompat private void setResultOnPreferenceClick(String key, final int result) { Preference pref = findPreference(key); - pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() - { - @Override - public boolean onPreferenceClick(Preference preference) + pref.setOnPreferenceClickListener( + new Preference.OnPreferenceClickListener() { - getActivity().setResult(result); - getActivity().finish(); - return true; - } - }); + @Override + public boolean onPreferenceClick(Preference preference) + { + getActivity().setResult(result); + getActivity().finish(); + return true; + } + }); } @Override @@ -87,19 +93,20 @@ public class SettingsFragment extends PreferenceFragmentCompat { super.onResume(); getPreferenceManager().getSharedPreferences(). - registerOnSharedPreferenceChangeListener(this); + registerOnSharedPreferenceChangeListener(this); } @Override public void onPause() { getPreferenceManager().getSharedPreferences(). - unregisterOnSharedPreferenceChangeListener(this); + unregisterOnSharedPreferenceChangeListener(this); super.onPause(); } @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { BackupManager.dataChanged("org.isoron.uhabits"); } @@ -107,11 +114,12 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public boolean onPreferenceTreeClick(Preference preference) { - if(preference.getKey() == null) return false; + if (preference.getKey() == null) return false; if (preference.getKey().equals("reminderSound")) { - ReminderUtils.startRingtonePickerActivity(this, RINGTONE_REQUEST_CODE); + ReminderUtils.startRingtonePickerActivity(this, + RINGTONE_REQUEST_CODE); return true; } @@ -121,7 +129,7 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if(requestCode == RINGTONE_REQUEST_CODE) + if (requestCode == RINGTONE_REQUEST_CODE) { ReminderUtils.parseRingtoneData(getContext(), data); updateRingtoneDescription(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java new file mode 100644 index 000000000..d7254b864 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Contains classes for the SettingsActivity. + */ +package org.isoron.uhabits.ui.settings; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/utils/Preferences.java b/app/src/main/java/org/isoron/uhabits/utils/Preferences.java index d4d318294..adb0c6086 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/Preferences.java +++ b/app/src/main/java/org/isoron/uhabits/utils/Preferences.java @@ -29,28 +29,40 @@ import org.isoron.uhabits.R; public class Preferences { - private static Preferences singleton; - private Context context; + private SharedPreferences prefs; - private Preferences() + public Preferences() { this.context = HabitsApplication.getContext(); prefs = PreferenceManager.getDefaultSharedPreferences(context); } - public static Preferences getInstance() + public Integer getDefaultHabitColor(int fallbackColor) { - if(singleton == null) singleton = new Preferences(); - return singleton; + return prefs.getInt("pref_default_habit_palette_color", fallbackColor); } - public void initialize() + /** + * Returns the number of the last hint shown to the user. + * + * @return number of last hint shown + */ + public int getLastHintNumber() { - PreferenceManager.setDefaultValues(context, R.xml.preferences, false); + return prefs.getInt("last_hint_number", -1); } + /** + * Returns the time when the last hint was shown to the user. + * + * @return timestamp of the day the last hint was shown + */ + public long getLastHintTimestamp() + { + return prefs.getLong("last_hint_timestamp", -1); + } public void incrementLaunchCount() { @@ -58,9 +70,9 @@ public class Preferences prefs.edit().putInt("launch_count", count + 1).apply(); } - public void updateLastAppVersion() + public void initialize() { - prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply(); + PreferenceManager.setDefaultValues(context, R.xml.preferences, false); } public boolean isFirstRun() @@ -73,19 +85,14 @@ public class Preferences prefs.edit().putBoolean("pref_first_run", isFirstRun).apply(); } - public void setLastHintTimestamp(long timestamp) - { - prefs.edit().putLong("last_hint_timestamp", timestamp).apply(); - } - public boolean isShortToggleEnabled() { return prefs.getBoolean("pref_short_toggle", false); } - public Integer getDefaultHabitColor(int defaultColor) + public void setShortToggleEnabled(boolean enabled) { - return prefs.getInt("pref_default_habit_palette_color", defaultColor); + prefs.edit().putBoolean("pref_short_toggle", enabled).apply(); } public void setDefaultHabitColor(int color) @@ -93,4 +100,38 @@ public class Preferences prefs.edit().putInt("pref_default_habit_palette_color", color).apply(); } + public void setShouldReverseCheckmarks(boolean shouldReverse) + { + prefs + .edit() + .putBoolean("pref_checkmark_reverse_order", shouldReverse) + .apply(); + } + + public boolean shouldReverseCheckmarks() + { + return prefs.getBoolean("pref_checkmark_reverse_order", false); + } + + public void updateLastAppVersion() + { + prefs.edit().putInt("last_version", BuildConfig.VERSION_CODE).apply(); + } + + /** + * Sets the last hint shown to the user, and the time that it was shown. + * + * @param number number of the last hint shown + * @param timestamp timestamp for the day the last hint was shown + */ + public void updateLastHint(int number, long timestamp) + { + prefs + .edit() + .putInt("last_hint_number", number) + .putLong("last_hint_timestamp", timestamp) + .apply(); + } + + } diff --git a/app/src/main/res/layout/list_habits_fragment.xml b/app/src/main/res/layout/list_habits.xml similarity index 56% rename from app/src/main/res/layout/list_habits_fragment.xml rename to app/src/main/res/layout/list_habits.xml index 9fba9281f..a9e71fa69 100644 --- a/app/src/main/res/layout/list_habits_fragment.xml +++ b/app/src/main/res/layout/list_habits.xml @@ -19,24 +19,36 @@ --> + android:background="?windowBackgroundColor"> - + + + + + app:drag_start_mode="onLongPress" + app:sort_enabled="true" + app:track_drag_sort="false" + android:layout_below="@id/header"/> - - - - - - - - - - - - + android:layout_alignParentBottom="true"/> - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/list_habits_activity.xml b/app/src/main/res/layout/list_habits_activity.xml deleted file mode 100644 index 6c0d3a643..000000000 --- a/app/src/main/res/layout/list_habits_activity.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/layout/list_habits_item.xml b/app/src/main/res/layout/list_habits_card.xml similarity index 63% rename from app/src/main/res/layout/list_habits_item.xml rename to app/src/main/res/layout/list_habits_card.xml index 63a766d35..3320a995c 100644 --- a/app/src/main/res/layout/list_habits_item.xml +++ b/app/src/main/res/layout/list_habits_card.xml @@ -18,31 +18,34 @@ ~ with this program. If not, see . --> - + + android:id="@+id/innerFrame" + style="@style/ListHabits.HabitCard" + android:layout_width="match_parent"> + android:layout_marginTop="0dp" + habit:thickness="3"/> + style="@style/ListHabits.Label"/> + + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/list_habits_item_check.xml b/app/src/main/res/layout/list_habits_card_checkmark.xml similarity index 100% rename from app/src/main/res/layout/list_habits_item_check.xml rename to app/src/main/res/layout/list_habits_card_checkmark.xml diff --git a/app/src/main/res/layout/list_habits_header_check.xml b/app/src/main/res/layout/list_habits_header_checkmark.xml similarity index 100% rename from app/src/main/res/layout/list_habits_header_check.xml rename to app/src/main/res/layout/list_habits_header_checkmark.xml diff --git a/app/src/main/res/layout/list_habits_hint.xml b/app/src/main/res/layout/list_habits_hint.xml new file mode 100644 index 000000000..11d86bd72 --- /dev/null +++ b/app/src/main/res/layout/list_habits_hint.xml @@ -0,0 +1,46 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_habits_preview.xml b/app/src/main/res/layout/list_habits_preview.xml new file mode 100644 index 000000000..ff52cd785 --- /dev/null +++ b/app/src/main/res/layout/list_habits_preview.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/list_habits_fragment.xml b/app/src/main/res/menu/list_habits_fragment.xml deleted file mode 100644 index 80695df7a..000000000 --- a/app/src/main/res/menu/list_habits_fragment.xml +++ /dev/null @@ -1,39 +0,0 @@ - - -

- - - - - - diff --git a/app/src/main/res/menu/main_activity.xml b/app/src/main/res/menu/main_activity.xml index c80f5945f..958594485 100644 --- a/app/src/main/res/menu/main_activity.xml +++ b/app/src/main/res/menu/main_activity.xml @@ -22,6 +22,20 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> + + + + ?headerBackgroundColor 2dp - match_parent - wrap_content - true 4dp + end \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d0c3dde42..94bf2a851 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -240,7 +240,6 @@ ?actionBarSize ?colorPrimary @style/ThemeOverlay.AppCompat.Dark.ActionBar - true diff --git a/app/src/main/res/values/styles_list_habits.xml b/app/src/main/res/values/styles_list_habits.xml index 10e079e0e..31bd516cf 100644 --- a/app/src/main/res/values/styles_list_habits.xml +++ b/app/src/main/res/values/styles_list_habits.xml @@ -26,9 +26,6 @@ diff --git a/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java new file mode 100644 index 000000000..f9d06ba8f --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java @@ -0,0 +1,45 @@ +/* + * 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; + +import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; +import org.isoron.uhabits.utils.Preferences; +import org.junit.Before; + +import javax.inject.Inject; + +public class BaseUnitTest +{ + @Inject + protected Preferences prefs; + + @Inject + protected HabitCardListCache listCache; + + protected TestComponent testComponent; + + @Before + public void setUp() + { + testComponent = DaggerTestComponent.builder().build(); + HabitsApplication.setComponent(testComponent); + testComponent.inject(this); + } +} diff --git a/app/src/test/java/org/isoron/uhabits/TestComponent.java b/app/src/test/java/org/isoron/uhabits/TestComponent.java new file mode 100644 index 000000000..8d73408e6 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/TestComponent.java @@ -0,0 +1,32 @@ +/* + * 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; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = {TestModule.class}) +public interface TestComponent extends BaseComponent +{ + void inject(BaseUnitTest baseUnitTest); +} + diff --git a/app/src/test/java/org/isoron/uhabits/TestModule.java b/app/src/test/java/org/isoron/uhabits/TestModule.java new file mode 100644 index 000000000..017b458f0 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/TestModule.java @@ -0,0 +1,56 @@ +/* + * 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; + +import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; +import org.isoron.uhabits.utils.Preferences; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +import static org.mockito.Mockito.mock; + +@Module +public class TestModule +{ + @Singleton + @Provides + Preferences providePreferences() + { + return mock(Preferences.class); + } + + @Singleton + @Provides + CommandRunner provideCommandRunner() + { + return mock(CommandRunner.class); + } + + @Singleton + @Provides + HabitCardListCache provideHabitCardListCache() + { + return mock(HabitCardListCache.class); + } +} diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapterTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapterTest.java new file mode 100644 index 000000000..13e153e31 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapterTest.java @@ -0,0 +1,112 @@ +/* + * 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.ui.habits.list.model; + +import org.isoron.uhabits.BaseUnitTest; +import org.junit.Before; +import org.junit.Test; + +public class HabitCardListAdapterTest extends BaseUnitTest +{ + @Override + @Before + public void setUp() + { + super.setUp(); + } + + @Test + public void testSelection() throws Exception + { + + } + + @Test + public void testGetCount() throws Exception + { + + } + + @Test + public void testGetItem() throws Exception + { + + } + + @Test + public void testGetItemId() throws Exception + { + + } + + @Test + public void testGetSelected() throws Exception + { + + } + + @Test + public void testGetView() throws Exception + { + + } + + @Test + public void testIsSelectionEmpty() throws Exception + { + + } + + @Test + public void testOnAttached() throws Exception + { + + } + + @Test + public void testOnCacheRefresh() throws Exception + { + + } + + @Test + public void testOnDetached() throws Exception + { + + } + + @Test + public void testReorder() throws Exception + { + + } + + @Test + public void testSetListView() throws Exception + { + + } + + @Test + public void testToggleSelection() throws Exception + { + + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonControllerTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonControllerTest.java new file mode 100644 index 000000000..6346c2540 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonControllerTest.java @@ -0,0 +1,95 @@ +/* + * 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.ui.habits.list.views; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class CheckmarkButtonControllerTest extends BaseUnitTest +{ + private CheckmarkButtonController controller; + + private CheckmarkButtonView view; + + private CheckmarkButtonController.Listener listener; + + private Habit habit; + + private int timestamp; + + @Override + @Before + public void setUp() + { + super.setUp(); + + timestamp = 0; + habit = mock(Habit.class); + + this.view = mock(CheckmarkButtonView.class); + this.listener = mock(CheckmarkButtonController.Listener.class); + this.controller = new CheckmarkButtonController(habit, timestamp); + controller.setView(view); + controller.setListener(listener); + } + + @Test + public void testOnClick_withShortToggle() throws Exception + { + doReturn(true).when(prefs).isShortToggleEnabled(); + controller.onClick(); + verifyToggle(); + } + + @Test + public void testOnClick_withoutShortToggle() throws Exception + { + doReturn(false).when(prefs).isShortToggleEnabled(); + controller.onClick(); + verifyInvalidToggle(); + } + + @Test + public void testOnLongClick() throws Exception + { + controller.onLongClick(); + verifyToggle(); + } + + protected void verifyInvalidToggle() + { + verifyZeroInteractions(view); + verify(listener).onInvalidToggle(); + } + + protected void verifyToggle() + { + verify(view).toggle(); + verify(listener).onToggle(habit, timestamp); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java new file mode 100644 index 000000000..2f7839589 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java @@ -0,0 +1,74 @@ +/* + * 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.ui.habits.list.views; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardController; +import org.isoron.uhabits.utils.DateUtils; +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class HabitCardControllerTest extends BaseUnitTest +{ + + private Habit habit; + + private HabitCardController controller; + + private HabitCardController.Listener listener; + + private HabitCardView view; + + @Override + @Before + public void setUp() + { + super.setUp(); + + this.habit = mock(Habit.class); + this.listener = mock(HabitCardController.Listener.class); + this.view = mock(HabitCardView.class); + + this.controller = new HabitCardController(); + controller.setListener(listener); + controller.setView(view); + view.setController(controller); + } + + @Test + public void testOnInvalidToggle() + { + controller.onInvalidToggle(); + verify(listener).onInvalidToggle(); + } + + @Test + public void testOnToggle() + { + long timestamp = DateUtils.getStartOfToday(); + controller.onToggle(habit, timestamp); + verify(view).triggerRipple(0, 0); + verify(listener).onToggle(habit, timestamp); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListControllerTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListControllerTest.java new file mode 100644 index 000000000..5a577d7fd --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListControllerTest.java @@ -0,0 +1,178 @@ +/* + * 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.ui.habits.list.views; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; +import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; +import org.junit.Before; +import org.junit.Test; + +import java.util.LinkedList; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +public class HabitCardListControllerTest extends BaseUnitTest +{ + + private LinkedList habits; + + private HabitCardListView view; + + private HabitCardListAdapter adapter; + + private HabitCardListController controller; + + private HabitCardListController.HabitListener habitListener; + + private HabitCardListController.SelectionListener selectionListener; + + @Override + @Before + public void setUp() + { + super.setUp(); + + this.view = mock(HabitCardListView.class); + this.adapter = mock(HabitCardListAdapter.class); + this.habitListener = mock(HabitCardListController.HabitListener.class); + this.selectionListener = + mock(HabitCardListController.SelectionListener.class); + + habits = new LinkedList<>(); + for (int i = 0; i < 10; i++) + { + Habit mock = mock(Habit.class); + habits.add(mock); + + } + + resetMocks(); + + this.controller = new HabitCardListController(adapter, view); + controller.setHabitListener(habitListener); + controller.setSelectionListener(selectionListener); + view.setController(controller); + } + + @Test + public void testClick_withSelection() + { + controller.onItemLongClick(0); + verify(adapter).toggleSelection(0); + verify(selectionListener).onSelectionStart(); + resetMocks(); + + controller.onItemClick(1); + verify(adapter).toggleSelection(1); + verify(selectionListener).onSelectionChange(); + resetMocks(); + + controller.onItemClick(1); + verify(adapter).toggleSelection(1); + verify(selectionListener).onSelectionChange(); + resetMocks(); + + doReturn(true).when(adapter).isSelectionEmpty(); + controller.onItemClick(0); + verify(adapter).toggleSelection(0); + verify(selectionListener).onSelectionFinish(); + } + + @Test + public void testClick_withoutSelection() + { + controller.onItemClick(0); + verify(habitListener).onHabitClick(habits.get(0)); + } + + @Test + public void testDragAndDrop_withSelection() + { + controller.onItemLongClick(0); + verify(adapter).toggleSelection(0); + verify(selectionListener).onSelectionStart(); + resetMocks(); + + controller.startDrag(1); + verify(selectionListener).onSelectionChange(); + verify(adapter).toggleSelection(1); + resetMocks(); + + controller.drop(1, 3); + verify(habitListener).onHabitReorder(habits.get(1), habits.get(3)); + verify(selectionListener).onSelectionFinish(); + verify(adapter).reorder(1, 3); + resetMocks(); + } + + @Test + public void testDragAndDrop_withoutSelection_distinctPlace() + { + controller.startDrag(0); + verify(selectionListener).onSelectionStart(); + verify(adapter).toggleSelection(0); + resetMocks(); + + controller.drop(0, 3); + verify(habitListener).onHabitReorder(habits.get(0), habits.get(3)); + verify(selectionListener).onSelectionFinish(); + verify(adapter).reorder(0, 3); + verify(adapter).clearSelection(); + } + + @Test + public void testInvalidToggle() + { + controller.onInvalidToggle(); + verify(habitListener).onInvalidToggle(); + } + + @Test + public void testLongClick_withSelection() + { + controller.onItemLongClick(0); + verify(adapter).toggleSelection(0); + verify(selectionListener).onSelectionStart(); + resetMocks(); + + controller.onItemLongClick(1); + verify(adapter).toggleSelection(1); + verify(selectionListener).onSelectionChange(); + } + + @Test + public void testToggle() + { + controller.onToggle(habits.getFirst(), 0); + verify(habitListener).onToggle(habits.getFirst(), 0); + } + + protected void resetMocks() + { + reset(adapter, habitListener, selectionListener); + for (int i = 0; i < habits.size(); i++) + doReturn(habits.get(i)).when(adapter).getItem(i); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/package-info.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/package-info.java new file mode 100644 index 000000000..67a18717a --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Contains views for ListHabitsActivity + */ +package org.isoron.uhabits.ui.habits.list.views; \ No newline at end of file diff --git a/build.gradle b/build.gradle index d9ea1210d..0382b84b4 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:2.1.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' + classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.2' + classpath 'me.tatarka:gradle-retrolambda:3.2.5' + classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } diff --git a/gradle.properties b/gradle.properties index 6c47b7f7d..57a1784b0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.parallel=true org.gradle.daemon=true -org.gradle.jvmargs=-Xms512m -Xmx2048m +org.gradle.jvmargs=-Xms1024m -Xmx4096m -XX:MaxPermSize=2048m From cabcd5b1bf65d601b3aab18f081f560cfe60629f Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 4 Jun 2016 22:06:01 -0400 Subject: [PATCH 015/184] CSV export: allow spaces on filename and fix tests --- .../java/org/isoron/uhabits/unit/models/HabitTest.java | 6 +++--- .../main/java/org/isoron/uhabits/io/HabitsCSVExporter.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java index f09664031..0d34272ec 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java @@ -366,9 +366,9 @@ public class HabitTest extends BaseTest HabitFixtures.createShortHabit(); String expectedCSV = - "Name,Description,NumRepetitions,Interval,Color\n" + - "Meditate,Did you meditate this morning?,1,1,#AFB42B\n" + - "Wake up early,Did you wake up before 6am?,2,3,#00897B\n"; + "Position,Name,Description,NumRepetitions,Interval,Color\n" + + "001,Meditate,Did you meditate this morning?,1,1,#AFB42B\n" + + "002,Wake up early,Did you wake up before 6am?,2,3,#00897B\n"; StringWriter writer = new StringWriter(); Habit.writeCSV(Habit.getAll(true), writer); diff --git a/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java b/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java index 67ddd3a15..b22da70e7 100644 --- a/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java @@ -81,7 +81,7 @@ public class HabitsCSVExporter @NonNull private String sanitizeFilename(String name) { - String s = name.replaceAll("[^a-zA-Z0-9\\._-]+", ""); + String s = name.replaceAll("[^ a-zA-Z0-9\\._-]+", ""); return s.substring(0, Math.min(s.length(), 100)); } From 78d4f86cab8d338a44f7778b7a51c6d9ca550802 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 10 Jun 2016 13:30:33 -0400 Subject: [PATCH 016/184] Separate ActiveAndroid from models --- app/build.gradle | 13 +- .../org/isoron/uhabits/BaseAndroidTest.java | 26 +- .../org/isoron/uhabits/ui/HabitMatchers.java | 4 +- .../uhabits/ui/MainActivityActions.java | 8 +- .../java/org/isoron/uhabits/ui/MainTest.java | 10 +- .../isoron/uhabits/unit/HabitFixtures.java | 157 ++--- .../commands/ArchiveHabitsCommandTest.java | 2 +- .../commands/ChangeHabitColorCommandTest.java | 9 +- .../unit/commands/CreateHabitCommandTest.java | 19 +- .../commands/DeleteHabitsCommandTest.java | 22 +- .../unit/commands/EditHabitCommandTest.java | 72 +-- .../commands/ToggleRepetitionCommandTest.java | 13 +- .../commands/UnarchiveHabitsCommandTest.java | 8 +- .../unit/io/HabitsCSVExporterTest.java | 69 +-- .../isoron/uhabits/unit/io/ImportTest.java | 49 +- .../unit/models/CheckmarkListTest.java | 175 ------ .../isoron/uhabits/unit/models/HabitTest.java | 378 ------------ .../unit/models/RepetitionListTest.java | 191 ------ .../uhabits/unit/models/ScoreListTest.java | 160 ++--- .../isoron/uhabits/unit/models/ScoreTest.java | 51 +- .../uhabits/unit/tasks/ExportCSVTaskTest.java | 19 +- .../list/view/CheckmarkButtonViewTest.java | 38 +- .../list/view/CheckmarkPanelViewTest.java | 5 +- .../unit/views/CheckmarkWidgetViewTest.java | 32 +- .../unit/views/HabitFrequencyViewTest.java | 23 +- .../unit/views/HabitHistoryViewTest.java | 82 +-- .../unit/views/HabitScoreViewTest.java | 35 +- .../unit/views/HabitStreakViewTest.java | 22 +- .../uhabits/unit/views/NumberViewTest.java | 77 --- .../uhabits/unit/views/RingViewTest.java | 6 +- .../isoron/uhabits/unit/views/ViewTest.java | 2 +- .../org/isoron/uhabits/AndroidComponent.java | 3 + .../org/isoron/uhabits/AndroidModule.java | 35 +- .../org/isoron/uhabits/BaseComponent.java | 54 +- .../uhabits/HabitBroadcastReceiver.java | 265 +++++---- .../org/isoron/uhabits/HabitsApplication.java | 68 ++- .../org/isoron/uhabits/HabitsBackupAgent.java | 3 + .../java/org/isoron/uhabits/MainActivity.java | 3 + .../commands/ArchiveHabitsCommand.java | 18 +- .../commands/ChangeHabitColorCommand.java | 47 +- .../org/isoron/uhabits/commands/Command.java | 13 +- .../uhabits/commands/CommandRunner.java | 10 + .../uhabits/commands/CreateHabitCommand.java | 31 +- .../uhabits/commands/DeleteHabitsCommand.java | 17 +- .../uhabits/commands/EditHabitCommand.java | 63 +- .../commands/ToggleRepetitionCommand.java | 5 +- .../commands/UnarchiveHabitsCommand.java | 18 +- .../isoron/uhabits/commands/package-info.java | 24 + .../isoron/uhabits/io/AbstractImporter.java | 17 + .../isoron/uhabits/io/GenericImporter.java | 13 +- .../uhabits/io/HabitBullCSVImporter.java | 19 +- .../isoron/uhabits/io/HabitsCSVExporter.java | 22 +- .../org/isoron/uhabits/io/LoopDBImporter.java | 12 +- .../isoron/uhabits/io/RewireDBImporter.java | 38 +- .../isoron/uhabits/io/TickmateDBImporter.java | 82 +-- .../org/isoron/uhabits/io/package-info.java | 23 + .../org/isoron/uhabits/models/Checkmark.java | 77 ++- .../isoron/uhabits/models/CheckmarkList.java | 293 ++++------ .../java/org/isoron/uhabits/models/Habit.java | 547 +++++++----------- .../org/isoron/uhabits/models/HabitList.java | 235 ++++++++ .../isoron/uhabits/models/ModelFactory.java | 37 ++ .../uhabits/models/ModelObservable.java | 41 +- .../org/isoron/uhabits/models/Repetition.java | 55 +- .../isoron/uhabits/models/RepetitionList.java | 230 ++++---- .../java/org/isoron/uhabits/models/Score.java | 117 ++-- .../org/isoron/uhabits/models/ScoreList.java | 380 +++++------- .../org/isoron/uhabits/models/Streak.java | 65 ++- .../org/isoron/uhabits/models/StreakList.java | 179 +++--- .../models/memory/MemoryCheckmarkList.java | 102 ++++ .../models/memory/MemoryHabitList.java | 111 ++++ .../models/memory/MemoryModelFactory.java | 61 ++ .../models/memory/MemoryRepetitionList.java | 106 ++++ .../models/memory/MemoryStreakList.java | 86 +++ .../uhabits/models/memory/package-info.java | 23 + .../isoron/uhabits/models/package-info.java | 24 + .../models/sqlite/CheckmarkRecord.java | 62 ++ .../uhabits/models/sqlite/HabitRecord.java | 177 ++++++ .../models/sqlite/RepetitionRecord.java | 58 ++ .../models/sqlite/SQLModelFactory.java | 64 ++ .../models/sqlite/SQLiteCheckmarkList.java | 139 +++++ .../models/sqlite/SQLiteHabitList.java | 232 ++++++++ .../models/sqlite/SQLiteRepetitionList.java | 143 +++++ .../models/sqlite/SQLiteScoreList.java | 173 ++++++ .../models/sqlite/SQLiteStreakList.java | 115 ++++ .../uhabits/models/sqlite/ScoreRecord.java | 65 +++ .../uhabits/models/sqlite/StreakRecord.java | 66 +++ .../uhabits/models/sqlite/package-info.java | 23 + .../java/org/isoron/uhabits/package-info.java | 23 + .../isoron/uhabits/tasks/package-info.java | 24 + .../org/isoron/uhabits/ui/BaseSystem.java | 10 +- .../isoron/uhabits/ui/about/package-info.java | 2 +- .../ui/habits/edit/BaseDialogFragment.java | 43 +- .../ui/habits/edit/BaseDialogHelper.java | 40 +- .../edit/CreateHabitDialogFragment.java | 8 +- .../habits/edit/EditHabitDialogFragment.java | 15 +- .../ui/habits/edit/HistoryEditorDialog.java | 12 +- .../uhabits/ui/habits/edit/package-info.java | 23 + .../ui/habits/list/ListHabitsActivity.java | 12 +- .../ui/habits/list/ListHabitsController.java | 18 +- .../ui/habits/list/ListHabitsScreen.java | 2 +- .../habits/list/controllers/package-info.java | 2 +- .../list/model/HabitCardListAdapter.java | 19 +- .../habits/list/model/HabitCardListCache.java | 18 +- .../ui/habits/list/model/package-info.java | 2 +- .../uhabits/ui/habits/list/package-info.java | 2 +- .../ui/habits/list/views/HabitCardView.java | 8 +- .../ui/habits/show/ShowHabitActivity.java | 12 +- .../ui/habits/show/ShowHabitFragment.java | 20 +- .../ui/habits/show/ShowHabitHelper.java | 21 +- .../uhabits/ui/habits/show/package-info.java | 24 + .../habits/show}/views/HabitDataView.java | 2 +- .../show}/views/HabitFrequencyView.java | 17 +- .../habits/show}/views/HabitHistoryView.java | 15 +- .../habits/show}/views/HabitScoreView.java | 455 ++++++++------- .../habits/show}/views/HabitStreakView.java | 257 ++++---- .../habits/show}/views/HabitWidgetView.java | 4 +- .../{ => ui/habits/show}/views/RingView.java | 4 +- .../show}/views/ScrollableDataView.java | 91 +-- .../ui/habits/show/views/package-info.java | 24 + .../isoron/uhabits/ui/intro/package-info.java | 2 +- .../org/isoron/uhabits/ui/package-info.java | 23 + .../uhabits/ui/settings/package-info.java | 2 +- .../org/isoron/uhabits/utils/ColorUtils.java | 118 ++-- .../isoron/uhabits/utils/DatabaseUtils.java | 78 +-- .../isoron/uhabits/utils/ReminderUtils.java | 146 +++-- .../isoron/uhabits/utils/package-info.java | 23 + .../org/isoron/uhabits/views/NumberView.java | 166 ------ .../uhabits/views/RepetitionCountView.java | 85 --- .../uhabits/widgets/BaseWidgetProvider.java | 14 +- .../widgets/CheckmarkWidgetProvider.java | 4 +- .../widgets/FrequencyWidgetProvider.java | 6 +- .../uhabits/widgets/HabitPickerDialog.java | 25 +- .../widgets/HistoryWidgetProvider.java | 6 +- .../uhabits/widgets/ScoreWidgetProvider.java | 8 +- .../uhabits/widgets/StreakWidgetProvider.java | 6 +- .../isoron/uhabits/widgets/package-info.java | 23 + .../views/CheckmarkWidgetView.java | 131 +++-- .../{ => widgets}/views/GraphWidgetView.java | 6 +- .../uhabits/widgets/views/package-info.java | 23 + app/src/main/res/layout/list_habits_card.xml | 2 +- app/src/main/res/layout/show_habit.xml | 10 +- app/src/main/res/layout/widget_checkmark.xml | 2 +- .../java/org/isoron/uhabits/BaseUnitTest.java | 31 + .../java/org/isoron/uhabits/TestModule.java | 25 + .../uhabits/models/CheckmarkListTest.java | 204 +++++++ .../isoron/uhabits/models/HabitFixtures.java | 92 +++ .../isoron/uhabits/models/HabitListTest.java | 200 +++++++ .../org/isoron/uhabits/models/HabitTest.java | 114 ++++ .../uhabits/models/RepetitionListTest.java | 179 ++++++ .../isoron/uhabits/models/StreakListTest.java | 149 +++++ build.gradle | 14 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 152 files changed, 6175 insertions(+), 3741 deletions(-) delete mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java delete mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java delete mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java delete mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java create mode 100644 app/src/main/java/org/isoron/uhabits/commands/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/io/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/HabitList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/ModelFactory.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/memory/MemoryCheckmarkList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/memory/MemoryHabitList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/memory/MemoryStreakList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/memory/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/CheckmarkRecord.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/HabitRecord.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/RepetitionRecord.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/ScoreRecord.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/StreakRecord.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/tasks/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/edit/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/package-info.java rename app/src/main/java/org/isoron/uhabits/{ => ui/habits/show}/views/HabitDataView.java (95%) rename app/src/main/java/org/isoron/uhabits/{ => ui/habits/show}/views/HabitFrequencyView.java (96%) rename app/src/main/java/org/isoron/uhabits/{ => ui/habits/show}/views/HabitHistoryView.java (97%) rename app/src/main/java/org/isoron/uhabits/{ => ui/habits/show}/views/HabitScoreView.java (80%) rename app/src/main/java/org/isoron/uhabits/{ => ui/habits/show}/views/HabitStreakView.java (73%) rename app/src/main/java/org/isoron/uhabits/{ => ui/habits/show}/views/HabitWidgetView.java (98%) rename app/src/main/java/org/isoron/uhabits/{ => ui/habits/show}/views/RingView.java (98%) rename app/src/main/java/org/isoron/uhabits/{ => ui/habits/show}/views/ScrollableDataView.java (83%) create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/views/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/package-info.java create mode 100644 app/src/main/java/org/isoron/uhabits/utils/package-info.java delete mode 100644 app/src/main/java/org/isoron/uhabits/views/NumberView.java delete mode 100644 app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java create mode 100644 app/src/main/java/org/isoron/uhabits/widgets/package-info.java rename app/src/main/java/org/isoron/uhabits/{ => widgets}/views/CheckmarkWidgetView.java (74%) rename app/src/main/java/org/isoron/uhabits/{ => widgets}/views/GraphWidgetView.java (91%) create mode 100644 app/src/main/java/org/isoron/uhabits/widgets/views/package-info.java create mode 100644 app/src/test/java/org/isoron/uhabits/models/CheckmarkListTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/models/HabitFixtures.java create mode 100644 app/src/test/java/org/isoron/uhabits/models/HabitListTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/models/HabitTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/models/RepetitionListTest.java create mode 100644 app/src/test/java/org/isoron/uhabits/models/StreakListTest.java diff --git a/app/build.gradle b/app/build.gradle index 08067b99c..318bc5d6c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,5 @@ apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' -apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'me.tatarka.retrolambda' android { @@ -31,6 +30,7 @@ android { lintOptions { checkReleaseBuilds false } + compileOptions { targetCompatibility 1.8 sourceCompatibility 1.8 @@ -77,14 +77,3 @@ dependencies { exclude group: 'com.android.support' } } - - -task grantAnimationPermission(type: Exec, dependsOn: 'installDebug') { - commandLine "adb shell pm grant org.isoron.uhabits android.permission.SET_ANIMATION_SCALE".split(' ') -} - -tasks.whenTaskAdded { task -> - if (task.name.startsWith('connected')) { - task.dependsOn grantAnimationPermission - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java index 8a5d6255a..8be833bc0 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java @@ -24,9 +24,11 @@ import android.os.Build; import android.os.Looper; import android.support.test.InstrumentationRegistry; +import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.unit.HabitFixtures; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.Preferences; import org.junit.Before; @@ -36,20 +38,29 @@ import javax.inject.Inject; public class BaseAndroidTest { - protected Context testContext; - protected Context targetContext; + // 8:00am, January 25th, 2015 (UTC) + public static final long FIXED_LOCAL_TIME = 1422172800000L; + private static boolean isLooperPrepared; - public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC) + protected Context testContext; + + protected Context targetContext; @Inject protected Preferences prefs; + + @Inject + protected HabitList habitList; + protected AndroidTestComponent androidTestComponent; + protected HabitFixtures habitFixtures; + @Before public void setUp() { - if(!isLooperPrepared) + if (!isLooperPrepared) { Looper.prepare(); isLooperPrepared = true; @@ -64,9 +75,12 @@ public class BaseAndroidTest androidTestComponent = DaggerAndroidTestComponent.builder().build(); HabitsApplication.setComponent(androidTestComponent); androidTestComponent.inject(this); + + habitFixtures = new HabitFixtures(habitList); } - protected void waitForAsyncTasks() throws InterruptedException, TimeoutException + protected void waitForAsyncTasks() + throws InterruptedException, TimeoutException { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java index ee8b810b8..3090d3dd0 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java @@ -39,7 +39,7 @@ public class HabitMatchers @Override public boolean matchesSafely(Habit habit) { - return habit.name.equals(name); + return habit.getName().equals(name); } @Override @@ -51,7 +51,7 @@ public class HabitMatchers @Override public void describeMismatchSafely(Habit habit, Description description) { - description.appendText("was ").appendText(habit.name); + description.appendText("was ").appendText(habit.getName()); } }; } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java index 7da8081ef..5b63b0b5b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java @@ -23,7 +23,7 @@ import android.support.test.espresso.NoMatchingViewException; import android.support.test.espresso.contrib.RecyclerViewActions; import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.sqlite.HabitRecord; import java.util.Collections; import java.util.LinkedList; @@ -93,7 +93,7 @@ public class MainActivityActions onView(withId(R.id.buttonSave)) .perform(click()); - onData(allOf(is(instanceOf(Habit.class)), withName(name))) + onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) .onChildView(withId(R.id.label)); return name; @@ -135,7 +135,7 @@ public class MainActivityActions boolean first = true; for(String name : names) { - onData(allOf(is(instanceOf(Habit.class)), withName(name))) + onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) .onChildView(withId(R.id.label)) .perform(first ? longClick() : click()); @@ -160,7 +160,7 @@ public class MainActivityActions public static void assertHabitsExist(List names) { for(String name : names) - onData(allOf(is(instanceOf(Habit.class)), withName(name))) + onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) .check(matches(isDisplayed())); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index 82732735d..9fba71854 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -30,8 +30,8 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; import org.isoron.uhabits.R; +import org.isoron.uhabits.models.sqlite.HabitRecord; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.MainActivity; import org.junit.After; import org.junit.Before; @@ -190,13 +190,13 @@ public class MainTest { String name = addHabit(true); - onData(allOf(is(instanceOf(Habit.class)), withName(name))) + onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) .onChildView(withId(R.id.checkmarkPanel)) .perform(toggleAllCheckmarks()); Thread.sleep(1200); - onData(allOf(is(instanceOf(Habit.class)), withName(name))) + onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) .onChildView(withId(R.id.label)) .perform(click()); @@ -217,7 +217,7 @@ public class MainTest { String name = addHabit(); - onData(allOf(is(instanceOf(Habit.class)), withName(name))) + onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) .onChildView(withId(R.id.label)) .perform(longClick()); @@ -247,7 +247,7 @@ public class MainTest { String name = addHabit(); - onData(allOf(is(instanceOf(Habit.class)), withName(name))) + onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) .onChildView(withId(R.id.label)) .perform(click()); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java index 16c16cccf..26f792ad5 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java @@ -19,151 +19,76 @@ package org.isoron.uhabits.unit; -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.Log; - -import org.isoron.uhabits.utils.DatabaseUtils; -import org.isoron.uhabits.utils.FileUtils; -import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.tasks.ExportDBTask; -import org.isoron.uhabits.tasks.ImportDataTask; - -import java.io.File; -import java.io.InputStream; -import java.util.Random; - -import static org.junit.Assert.fail; +import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.utils.DateUtils; public class HabitFixtures { - public static boolean NON_DAILY_HABIT_CHECKS[] = { true, false, false, true, true, true, false, - false, true, true }; + public boolean NON_DAILY_HABIT_CHECKS[] = { + true, false, false, true, true, true, false, false, true, true + }; - public static Habit createShortHabit() - { - Habit habit = new Habit(); - habit.name = "Wake up early"; - habit.description = "Did you wake up before 6am?"; - habit.freqNum = 2; - habit.freqDen = 3; - habit.save(); + private final HabitList habitList; - long timestamp = DateUtils.getStartOfToday(); - for(boolean c : NON_DAILY_HABIT_CHECKS) - { - if(c) habit.repetitions.toggle(timestamp); - timestamp -= DateUtils.millisecondsInOneDay; - } - - return habit; + public HabitFixtures(HabitList habitList) + { + this.habitList = habitList; } - public static Habit createEmptyHabit() + public Habit createEmptyHabit() { Habit habit = new Habit(); - habit.name = "Meditate"; - habit.description = "Did you meditate this morning?"; - habit.color = 3; - habit.freqNum = 1; - habit.freqDen = 1; - habit.save(); + habit.setName("Meditate"); + habit.setDescription("Did you meditate this morning?"); + habit.setColor(3); + habit.setFreqNum(1); + habit.setFreqDen(1); + habitList.add(habit); return habit; } - public static Habit createLongHabit() + public Habit createLongHabit() { Habit habit = createEmptyHabit(); - habit.freqNum = 3; - habit.freqDen = 7; - habit.color = 4; - habit.save(); + habit.setFreqNum(3); + habit.setFreqDen(7); + habit.setColor(4); long day = DateUtils.millisecondsInOneDay; long today = DateUtils.getStartOfToday(); - int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, 28, 50, 51, 52, - 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 81, 83, 89, 90, 91, 95, - 102, 103, 108, 109, 120}; + int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, + 28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, + 81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120}; - for(int mark : marks) - habit.repetitions.toggle(today - mark * day); + for (int mark : marks) + habit.getRepetitions().toggleTimestamp(today - mark * day); return habit; } - public static void generateHugeDataSet() throws Throwable + public Habit createShortHabit() { - final int nHabits = 30; - final int nYears = 5; - - DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() - { - @Override - public void execute() - { - Random rand = new Random(); - - for(int i = 0; i < nHabits; i++) - { - Log.i("HabitFixture", String.format("Creating habit %d / %d", i, nHabits)); - - Habit habit = new Habit(); - habit.name = String.format("Habit %d", i); - habit.save(); - - long today = DateUtils.getStartOfToday(); - long day = DateUtils.millisecondsInOneDay; - - - for(int j = 0; j < 365 * nYears; j++) - { - if(rand.nextBoolean()) - habit.repetitions.toggle(today - j * day); - } - - habit.scores.getTodayValue(); - habit.streaks.getAll(1); - } - } - }); + Habit habit = new Habit(); + habit.setName("Wake up early"); + habit.setDescription("Did you wake up before 6am?"); + habit.setFreqNum(2); + habit.setFreqDen(3); + habitList.add(habit); - ExportDBTask task = new ExportDBTask(null); - task.setListener(new ExportDBTask.Listener() + long timestamp = DateUtils.getStartOfToday(); + for (boolean c : NON_DAILY_HABIT_CHECKS) { - @Override - public void onExportDBFinished(@Nullable String filename) - { - if(filename != null) - Log.i("HabitFixture", String.format("Huge data set exported to %s", filename)); - else - Log.i("HabitFixture", "Failed to save database"); - } - }); - task.execute(); - - BaseTask.waitForTasks(30000); - } - - public static void loadHugeDataSet(Context testContext) throws Throwable - { - File baseDir = FileUtils.getFilesDir("Backups"); - if(baseDir == null) fail("baseDir should not be null"); - - File dst = new File(String.format("%s/%s", baseDir.getPath(), "loopHuge.db")); - InputStream in = testContext.getAssets().open("fixtures/loopHuge.db"); - FileUtils.copy(in, dst); - - ImportDataTask task = new ImportDataTask(dst, null); - task.execute(); + if (c) habit.getRepetitions().toggleTimestamp(timestamp); + timestamp -= DateUtils.millisecondsInOneDay; + } - BaseTask.waitForTasks(30000); + return habit; } - public static void purgeHabits() + public void purgeHabits(HabitList habitList) { - for(Habit h : Habit.getAll(true)) - h.cascadeDelete(); + for (Habit h : habitList.getAll(true)) + habitList.remove(h); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java index d00881506..57a43817a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java @@ -48,7 +48,7 @@ public class ArchiveHabitsCommandTest extends BaseAndroidTest { super.setUp(); - habit = HabitFixtures.createShortHabit(); + habit = habitFixtures.createShortHabit(); command = new ArchiveHabitsCommand(Collections.singletonList(habit)); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java index dd9f334f4..bfff4f81b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java @@ -51,9 +51,8 @@ public class ChangeHabitColorCommandTest extends BaseAndroidTest for(int i = 0; i < 3; i ++) { - Habit habit = HabitFixtures.createShortHabit(); - habit.color = i+1; - habit.save(); + Habit habit = habitFixtures.createShortHabit(); + habit.setColor(i + 1); habits.add(habit); } @@ -79,12 +78,12 @@ public class ChangeHabitColorCommandTest extends BaseAndroidTest { int k = 0; for(Habit h : habits) - assertThat(h.color, equalTo(++k)); + assertThat(h.getColor(), equalTo(++k)); } private void checkNewColors() { for(Habit h : habits) - assertThat(h.color, equalTo(0)); + assertThat(h.getColor(), equalTo(0)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java index 60ee83b9a..c5a9f39f2 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java @@ -23,9 +23,9 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,6 +42,7 @@ public class CreateHabitCommandTest extends BaseAndroidTest { private CreateHabitCommand command; + private Habit model; @Before @@ -50,36 +51,36 @@ public class CreateHabitCommandTest extends BaseAndroidTest super.setUp(); model = new Habit(); - model.name = "New habit"; + model.setName("New habit"); command = new CreateHabitCommand(model); - HabitFixtures.purgeHabits(); + habitFixtures.purgeHabits(habitList); } @Test public void testExecuteUndoRedo() { - assertTrue(Habit.getAll(true).isEmpty()); + assertTrue(habitList.getAll(true).isEmpty()); command.execute(); - List allHabits = Habit.getAll(true); + List allHabits = habitList.getAll(true); assertThat(allHabits.size(), equalTo(1)); Habit habit = allHabits.get(0); Long id = habit.getId(); - assertThat(habit.name, equalTo(model.name)); + assertThat(habit.getName(), equalTo(model.getName())); command.undo(); - assertTrue(Habit.getAll(true).isEmpty()); + assertTrue(habitList.getAll(true).isEmpty()); command.execute(); - allHabits = Habit.getAll(true); + allHabits = habitList.getAll(true); assertThat(allHabits.size(), equalTo(1)); habit = allHabits.get(0); Long newId = habit.getId(); assertThat(id, equalTo(newId)); - assertThat(habit.name, equalTo(model.name)); + assertThat(habit.getName(), equalTo(model.getName())); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java index 045529c04..983448f41 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java @@ -25,7 +25,6 @@ import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -42,30 +41,31 @@ import static org.hamcrest.Matchers.equalTo; public class DeleteHabitsCommandTest extends BaseAndroidTest { private DeleteHabitsCommand command; + private LinkedList habits; @Rule public ExpectedException thrown = ExpectedException.none(); + @Override @Before public void setUp() { super.setUp(); - HabitFixtures.purgeHabits(); + habitFixtures.purgeHabits(habitList); habits = new LinkedList<>(); - // Habits that shuold be deleted - for(int i = 0; i < 3; i ++) + // Habits that should be deleted + for (int i = 0; i < 3; i++) { - Habit habit = HabitFixtures.createShortHabit(); + Habit habit = habitFixtures.createShortHabit(); habits.add(habit); } // Extra habit that should not be deleted - Habit extraHabit = HabitFixtures.createShortHabit(); - extraHabit.name = "extra"; - extraHabit.save(); + Habit extraHabit = habitFixtures.createShortHabit(); + extraHabit.setName("extra"); command = new DeleteHabitsCommand(habits); } @@ -73,11 +73,11 @@ public class DeleteHabitsCommandTest extends BaseAndroidTest @Test public void testExecuteUndoRedo() { - assertThat(Habit.getAll(true).size(), equalTo(4)); + assertThat(habitList.getAll(true).size(), equalTo(4)); command.execute(); - assertThat(Habit.getAll(true).size(), equalTo(1)); - assertThat(Habit.getAll(true).get(0).name, equalTo("extra")); + assertThat(habitList.getAll(true).size(), equalTo(1)); + assertThat(habitList.getAll(true).get(0).getName(), equalTo("extra")); thrown.expect(UnsupportedOperationException.class); command.undo(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java index a3c04fd82..46f077481 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java @@ -25,12 +25,10 @@ import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.EditHabitCommand; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static junit.framework.Assert.assertTrue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; @@ -41,25 +39,25 @@ public class EditHabitCommandTest extends BaseAndroidTest { private EditHabitCommand command; + private Habit habit; + private Habit modified; - private Long id; + @Override @Before public void setUp() { super.setUp(); - habit = HabitFixtures.createShortHabit(); - habit.name = "original"; - habit.freqDen = 1; - habit.freqNum = 1; - habit.save(); - - id = habit.getId(); + habit = habitFixtures.createShortHabit(); + habit.setName("original"); + habit.setFreqDen(1); + habit.setFreqNum(1); - modified = new Habit(habit); - modified.name = "modified"; + modified = new Habit(); + modified.copyFrom(habit); + modified.setName("modified"); } @Test @@ -67,54 +65,44 @@ public class EditHabitCommandTest extends BaseAndroidTest { command = new EditHabitCommand(habit, modified); - int originalScore = habit.scores.getTodayValue(); - assertThat(habit.name, equalTo("original")); + int originalScore = habit.getScores().getTodayValue(); + assertThat(habit.getName(), equalTo("original")); command.execute(); - refreshHabit(); - assertThat(habit.name, equalTo("modified")); - assertThat(habit.scores.getTodayValue(), equalTo(originalScore)); + assertThat(habit.getName(), equalTo("modified")); + assertThat(habit.getScores().getTodayValue(), equalTo(originalScore)); command.undo(); - refreshHabit(); - assertThat(habit.name, equalTo("original")); - assertThat(habit.scores.getTodayValue(), equalTo(originalScore)); + assertThat(habit.getName(), equalTo("original")); + assertThat(habit.getScores().getTodayValue(), equalTo(originalScore)); command.execute(); - refreshHabit(); - assertThat(habit.name, equalTo("modified")); - assertThat(habit.scores.getTodayValue(), equalTo(originalScore)); + assertThat(habit.getName(), equalTo("modified")); + assertThat(habit.getScores().getTodayValue(), equalTo(originalScore)); } @Test public void testExecuteUndoRedo_withModifiedInterval() { - modified.freqNum = 1; - modified.freqDen = 7; + modified.setFreqNum(1); + modified.setFreqDen(7); command = new EditHabitCommand(habit, modified); - int originalScore = habit.scores.getTodayValue(); - assertThat(habit.name, equalTo("original")); + int originalScore = habit.getScores().getTodayValue(); + assertThat(habit.getName(), equalTo("original")); command.execute(); - refreshHabit(); - assertThat(habit.name, equalTo("modified")); - assertThat(habit.scores.getTodayValue(), greaterThan(originalScore)); + assertThat(habit.getName(), equalTo("modified")); + assertThat(habit.getScores().getTodayValue(), + greaterThan(originalScore)); command.undo(); - refreshHabit(); - assertThat(habit.name, equalTo("original")); - assertThat(habit.scores.getTodayValue(), equalTo(originalScore)); + assertThat(habit.getName(), equalTo("original")); + assertThat(habit.getScores().getTodayValue(), equalTo(originalScore)); command.execute(); - refreshHabit(); - assertThat(habit.name, equalTo("modified")); - assertThat(habit.scores.getTodayValue(), greaterThan(originalScore)); - } - - private void refreshHabit() - { - habit = Habit.get(id); - assertTrue(habit != null); + assertThat(habit.getName(), equalTo("modified")); + assertThat(habit.getScores().getTodayValue(), + greaterThan(originalScore)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java index fb22aea6b..e14c35ad9 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java @@ -24,9 +24,8 @@ import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; +import org.isoron.uhabits.utils.DateUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,7 +47,7 @@ public class ToggleRepetitionCommandTest extends BaseAndroidTest { super.setUp(); - habit = HabitFixtures.createShortHabit(); + habit = habitFixtures.createShortHabit(); today = DateUtils.getStartOfToday(); command = new ToggleRepetitionCommand(habit, today); @@ -57,15 +56,15 @@ public class ToggleRepetitionCommandTest extends BaseAndroidTest @Test public void testExecuteUndoRedo() { - assertTrue(habit.repetitions.contains(today)); + assertTrue(habit.getRepetitions().containsTimestamp(today)); command.execute(); - assertFalse(habit.repetitions.contains(today)); + assertFalse(habit.getRepetitions().containsTimestamp(today)); command.undo(); - assertTrue(habit.repetitions.contains(today)); + assertTrue(habit.getRepetitions().containsTimestamp(today)); command.execute(); - assertFalse(habit.repetitions.contains(today)); + assertFalse(habit.getRepetitions().containsTimestamp(today)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java index 336ee5752..07cc11104 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java @@ -25,7 +25,6 @@ import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,17 +38,18 @@ import static junit.framework.Assert.assertTrue; @SmallTest public class UnarchiveHabitsCommandTest extends BaseAndroidTest { - private UnarchiveHabitsCommand command; private Habit habit; + @Override @Before public void setUp() { super.setUp(); - habit = HabitFixtures.createShortHabit(); - Habit.archive(Collections.singletonList(habit)); + habit = habitFixtures.createShortHabit(); + habit.setArchived(1); + habitList.update(habit); command = new UnarchiveHabitsCommand(Collections.singletonList(habit)); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java index b68ec0a71..a965a7eb9 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java @@ -25,10 +25,9 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; +import org.isoron.uhabits.utils.FileUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,41 +53,18 @@ public class HabitsCSVExporterTest extends BaseAndroidTest { super.setUp(); - HabitFixtures.purgeHabits(); - HabitFixtures.createShortHabit(); - HabitFixtures.createEmptyHabit(); + habitFixtures.purgeHabits(habitList); + habitFixtures.createShortHabit(); + habitFixtures.createEmptyHabit(); Context targetContext = InstrumentationRegistry.getTargetContext(); baseDir = targetContext.getCacheDir(); } - private void unzip(File file) throws IOException - { - ZipFile zip = new ZipFile(file); - Enumeration e = zip.entries(); - - while(e.hasMoreElements()) - { - ZipEntry entry = e.nextElement(); - InputStream stream = zip.getInputStream(entry); - - String outputFilename = String.format("%s/%s", baseDir.getAbsolutePath(), - entry.getName()); - File outputFile = new File(outputFilename); - - File parent = outputFile.getParentFile(); - if(parent != null) parent.mkdirs(); - - FileUtils.copy(stream, outputFile); - } - - zip.close(); - } - @Test public void testExportCSV() throws IOException { - List habits = Habit.getAll(true); + List habits = habitList.getAll(true); HabitsCSVExporter exporter = new HabitsCSVExporter(habits, baseDir); String filename = exporter.writeArchive(); @@ -105,14 +81,41 @@ public class HabitsCSVExporterTest extends BaseAndroidTest assertPathExists("002 Meditate/Scores.csv"); } + private void assertAbsolutePathExists(String s) + { + File file = new File(s); + assertTrue( + String.format("File %s should exist", file.getAbsolutePath()), + file.exists()); + } + private void assertPathExists(String s) { - assertAbsolutePathExists(String.format("%s/%s", baseDir.getAbsolutePath(), s)); + assertAbsolutePathExists( + String.format("%s/%s", baseDir.getAbsolutePath(), s)); } - private void assertAbsolutePathExists(String s) + private void unzip(File file) throws IOException { - File file = new File(s); - assertTrue(String.format("File %s should exist", file.getAbsolutePath()), file.exists()); + ZipFile zip = new ZipFile(file); + Enumeration e = zip.entries(); + + while (e.hasMoreElements()) + { + ZipEntry entry = e.nextElement(); + InputStream stream = zip.getInputStream(entry); + + String outputFilename = + String.format("%s/%s", baseDir.getAbsolutePath(), + entry.getName()); + File outputFile = new File(outputFilename); + + File parent = outputFile.getParentFile(); + if (parent != null) parent.mkdirs(); + + FileUtils.copy(stream, outputFile); + } + + zip.close(); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java index 6ca6ba60a..4f73471b0 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java @@ -25,11 +25,10 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; +import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.io.GenericImporter; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -60,7 +59,7 @@ public class ImportTest extends BaseAndroidTest super.setUp(); DateUtils.setFixedLocalTime(null); - HabitFixtures.purgeHabits(); + habitFixtures.purgeHabits(habitList); context = InstrumentationRegistry.getInstrumentation().getContext(); baseDir = FileUtils.getFilesDir("Backups"); if(baseDir == null) fail("baseDir should not be null"); @@ -89,7 +88,7 @@ public class ImportTest extends BaseAndroidTest { GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); date.set(year, month - 1, day); - return h.repetitions.contains(date.getTimeInMillis()); + return h.getRepetitions().containsTimestamp(date.getTimeInMillis()); } @Test @@ -97,11 +96,11 @@ public class ImportTest extends BaseAndroidTest { importFromFile("tickmate.db"); - List habits = Habit.getAll(true); + List habits = habitList.getAll(true); assertThat(habits.size(), equalTo(3)); Habit h = habits.get(0); - assertThat(h.name, equalTo("Vegan")); + assertThat(h.getName(), equalTo("Vegan")); assertTrue(containsRepetition(h, 2016, 1, 24)); assertTrue(containsRepetition(h, 2016, 2, 5)); assertTrue(containsRepetition(h, 2016, 3, 18)); @@ -113,13 +112,13 @@ public class ImportTest extends BaseAndroidTest { importFromFile("rewire.db"); - List habits = Habit.getAll(true); + List habits = habitList.getAll(true); assertThat(habits.size(), equalTo(3)); Habit habit = habits.get(0); - assertThat(habit.name, equalTo("Wake up early")); - assertThat(habit.freqNum, equalTo(3)); - assertThat(habit.freqDen, equalTo(7)); + assertThat(habit.getName(), equalTo("Wake up early")); + assertThat(habit.getFreqNum(), equalTo(3)); + assertThat(habit.getFreqDen(), equalTo(7)); assertFalse(habit.hasReminder()); assertFalse(containsRepetition(habit, 2015, 12, 31)); assertTrue(containsRepetition(habit, 2016, 1, 18)); @@ -127,13 +126,13 @@ public class ImportTest extends BaseAndroidTest assertFalse(containsRepetition(habit, 2016, 3, 10)); habit = habits.get(1); - assertThat(habit.name, equalTo("brush teeth")); - assertThat(habit.freqNum, equalTo(3)); - assertThat(habit.freqDen, equalTo(7)); - assertThat(habit.reminderHour, equalTo(8)); - assertThat(habit.reminderMin, equalTo(0)); + assertThat(habit.getName(), equalTo("brush teeth")); + assertThat(habit.getFreqNum(), equalTo(3)); + assertThat(habit.getFreqDen(), equalTo(7)); + assertThat(habit.getReminderHour(), equalTo(8)); + assertThat(habit.getReminderMin(), equalTo(0)); boolean[] reminderDays = {false, true, true, true, true, true, false}; - assertThat(habit.reminderDays, equalTo(DateUtils.packWeekdayList(reminderDays))); + assertThat(habit.getReminderDays(), equalTo(DateUtils.packWeekdayList(reminderDays))); } @Test @@ -141,14 +140,14 @@ public class ImportTest extends BaseAndroidTest { importFromFile("habitbull.csv"); - List habits = Habit.getAll(true); + List habits = habitList.getAll(true); assertThat(habits.size(), equalTo(4)); Habit habit = habits.get(0); - assertThat(habit.name, equalTo("Breed dragons")); - assertThat(habit.description, equalTo("with love and fire")); - assertThat(habit.freqNum, equalTo(1)); - assertThat(habit.freqDen, equalTo(1)); + assertThat(habit.getName(), equalTo("Breed dragons")); + assertThat(habit.getDescription(), equalTo("with love and fire")); + assertThat(habit.getFreqNum(), equalTo(1)); + assertThat(habit.getFreqDen(), equalTo(1)); assertTrue(containsRepetition(habit, 2016, 3, 18)); assertTrue(containsRepetition(habit, 2016, 3, 19)); assertFalse(containsRepetition(habit, 2016, 3, 20)); @@ -159,13 +158,13 @@ public class ImportTest extends BaseAndroidTest { importFromFile("loop.db"); - List habits = Habit.getAll(true); + List habits = habitList.getAll(true); assertThat(habits.size(), equalTo(9)); Habit habit = habits.get(0); - assertThat(habit.name, equalTo("Wake up early")); - assertThat(habit.freqNum, equalTo(3)); - assertThat(habit.freqDen, equalTo(7)); + assertThat(habit.getName(), equalTo("Wake up early")); + assertThat(habit.getFreqNum(), equalTo(3)); + assertThat(habit.getFreqDen(), equalTo(7)); assertTrue(containsRepetition(habit, 2016, 3, 14)); assertTrue(containsRepetition(habit, 2016, 3, 16)); assertFalse(containsRepetition(habit, 2016, 3, 17)); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java deleted file mode 100644 index 9af522aef..000000000 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/CheckmarkListTest.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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.unit.models; - -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; - -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; -import java.io.StringWriter; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY; -import static org.isoron.uhabits.models.Checkmark.CHECKED_IMPLICITLY; -import static org.isoron.uhabits.models.Checkmark.UNCHECKED; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class CheckmarkListTest extends BaseAndroidTest -{ - Habit nonDailyHabit; - private Habit emptyHabit; - - @Before - public void setUp() - { - super.setUp(); - - HabitFixtures.purgeHabits(); - nonDailyHabit = HabitFixtures.createShortHabit(); - emptyHabit = HabitFixtures.createEmptyHabit(); - } - - @After - public void tearDown() - { - DateUtils.setFixedLocalTime(null); - } - - @Test - public void test_getAllValues_withNonDailyHabit() - { - int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY, - CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED, - CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY }; - - int[] actualValues = nonDailyHabit.checkmarks.getAllValues(); - - assertThat(actualValues, equalTo(expectedValues)); - } - - @Test - public void test_getAllValues_withEmptyHabit() - { - int[] expectedValues = new int[0]; - int[] actualValues = emptyHabit.checkmarks.getAllValues(); - - assertThat(actualValues, equalTo(expectedValues)); - } - - @Test - public void test_getAllValues_moveForwardInTime() - { - travelInTime(3); - - int[] expectedValues = { UNCHECKED, UNCHECKED, UNCHECKED, CHECKED_EXPLICITLY, UNCHECKED, - CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, - UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY }; - - int[] actualValues = nonDailyHabit.checkmarks.getAllValues(); - - assertThat(actualValues, equalTo(expectedValues)); - } - - @Test - public void test_getAllValues_moveBackwardsInTime() - { - travelInTime(-3); - - int[] expectedValues = { CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, - UNCHECKED, CHECKED_IMPLICITLY, CHECKED_EXPLICITLY, CHECKED_EXPLICITLY }; - - int[] actualValues = nonDailyHabit.checkmarks.getAllValues(); - - assertThat(actualValues, equalTo(expectedValues)); - } - - @Test - public void test_getValues_withInvalidInterval() - { - int values[] = nonDailyHabit.checkmarks.getValues(100L, -100L); - assertThat(values, equalTo(new int[0])); - } - - @Test - public void test_getValues_withValidInterval() - { - long from = DateUtils.getStartOfToday() - 15 * DateUtils.millisecondsInOneDay; - long to = DateUtils.getStartOfToday() - 5 * DateUtils.millisecondsInOneDay; - - int[] expectedValues = { CHECKED_EXPLICITLY, UNCHECKED, CHECKED_IMPLICITLY, - CHECKED_EXPLICITLY, CHECKED_EXPLICITLY, UNCHECKED, UNCHECKED, UNCHECKED, UNCHECKED, - UNCHECKED, UNCHECKED }; - - int[] actualValues = nonDailyHabit.checkmarks.getValues(from, to); - - assertThat(actualValues, equalTo(expectedValues)); - } - - @Test - public void test_getTodayValue() - { - travelInTime(-1); - assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(UNCHECKED)); - - travelInTime(0); - assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(CHECKED_EXPLICITLY)); - - travelInTime(1); - assertThat(nonDailyHabit.checkmarks.getTodayValue(), equalTo(UNCHECKED)); - } - - @Test - public void test_writeCSV() throws IOException - { - String expectedCSV = - "2015-01-16,2\n" + - "2015-01-17,2\n" + - "2015-01-18,1\n" + - "2015-01-19,0\n" + - "2015-01-20,2\n" + - "2015-01-21,2\n" + - "2015-01-22,2\n" + - "2015-01-23,1\n" + - "2015-01-24,0\n" + - "2015-01-25,2\n"; - - StringWriter writer = new StringWriter(); - nonDailyHabit.checkmarks.writeCSV(writer); - - assertThat(writer.toString(), equalTo(expectedCSV)); - } - - private void travelInTime(int days) - { - DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME + - days * DateUtils.millisecondsInOneDay); - } -} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java deleted file mode 100644 index 265cd80f1..000000000 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/HabitTest.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * 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.unit.models; - -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; - -import org.hamcrest.MatcherAssert; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.LinkedList; -import java.util.List; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.core.IsNot.not; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class HabitTest extends BaseAndroidTest -{ - @Before - public void setUp() - { - super.setUp(); - HabitFixtures.purgeHabits(); - } - - @Test - public void testConstructor_default() - { - Habit habit = new Habit(); - assertThat(habit.archived, is(0)); - assertThat(habit.highlight, is(0)); - - assertThat(habit.reminderHour, is(nullValue())); - assertThat(habit.reminderMin, is(nullValue())); - - assertThat(habit.reminderDays, is(not(nullValue()))); - assertThat(habit.streaks, is(not(nullValue()))); - assertThat(habit.scores, is(not(nullValue()))); - assertThat(habit.repetitions, is(not(nullValue()))); - assertThat(habit.checkmarks, is(not(nullValue()))); - } - - @Test - public void testConstructor_habit() - { - Habit model = new Habit(); - model.archived = 1; - model.highlight = 1; - model.color = 0; - model.freqNum = 10; - model.freqDen = 20; - model.reminderDays = 1; - model.reminderHour = 8; - model.reminderMin = 30; - model.position = 0; - - Habit habit = new Habit(model); - assertThat(habit.archived, is(model.archived)); - assertThat(habit.highlight, is(model.highlight)); - assertThat(habit.color, is(model.color)); - assertThat(habit.freqNum, is(model.freqNum)); - assertThat(habit.freqDen, is(model.freqDen)); - assertThat(habit.reminderDays, is(model.reminderDays)); - assertThat(habit.reminderHour, is(model.reminderHour)); - assertThat(habit.reminderMin, is(model.reminderMin)); - assertThat(habit.position, is(model.position)); - } - - @Test - public void test_get_withValidId() - { - Habit habit = new Habit(); - habit.save(); - - Habit habit2 = Habit.get(habit.getId()); - assertThat(habit, equalTo(habit2)); - } - - @Test - public void test_get_withInvalidId() - { - Habit habit = Habit.get(123456L); - assertThat(habit, is(nullValue())); - } - - @Test - public void test_getAll_withoutArchived() - { - List habits = new LinkedList<>(); - List habitsWithArchived = new LinkedList<>(); - - for(int i = 0; i < 10; i++) - { - Habit h = new Habit(); - - if(i % 2 == 0) - h.archived = 1; - else - habits.add(h); - - habitsWithArchived.add(h); - h.save(); - } - - assertThat(habits, equalTo(Habit.getAll(false))); - assertThat(habitsWithArchived, equalTo(Habit.getAll(true))); - } - - @Test - public void test_getByPosition() - { - List habits = new LinkedList<>(); - - for(int i = 0; i < 10; i++) - { - Habit h = new Habit(); - h.save(); - habits.add(h); - } - - for(int i = 0; i < 10; i++) - { - Habit h = Habit.getByPosition(i); - if(h == null) fail(); - assertThat(h, equalTo(habits.get(i))); - } - } - - @Test - public void test_count() - { - for(int i = 0; i < 10; i++) - { - Habit h = new Habit(); - if(i % 2 == 0) h.archived = 1; - h.save(); - } - - assertThat(Habit.count(), equalTo(5)); - } - - - @Test - public void test_countWithArchived() - { - for(int i = 0; i < 10; i++) - { - Habit h = new Habit(); - if(i % 2 == 0) h.archived = 1; - h.save(); - } - - assertThat(Habit.countWithArchived(), equalTo(10)); - } - - @Test - public void test_updateId() - { - Habit habit = new Habit(); - habit.name = "Hello World"; - habit.save(); - - Long oldId = habit.getId(); - Long newId = 123456L; - Habit.updateId(oldId, newId); - - Habit newHabit = Habit.get(newId); - if(newHabit == null) fail(); - assertThat(newHabit, is(not(nullValue()))); - assertThat(newHabit.name, equalTo(habit.name)); - } - - @Test - public void test_reorder() - { - List ids = new LinkedList<>(); - - int n = 10; - for (int i = 0; i < n; i++) - { - Habit h = new Habit(); - h.save(); - ids.add(h.getId()); - assertThat(h.position, is(i)); - } - - int operations[][] = { - {5, 2}, - {3, 7}, - {4, 4}, - {3, 2} - }; - - int expectedPosition[][] = { - {0, 1, 3, 4, 5, 2, 6, 7, 8, 9}, - {0, 1, 7, 3, 4, 2, 5, 6, 8, 9}, - {0, 1, 7, 3, 4, 2, 5, 6, 8, 9}, - {0, 1, 7, 2, 4, 3, 5, 6, 8, 9}, - }; - - for(int i = 0; i < operations.length; i++) - { - int from = operations[i][0]; - int to = operations[i][1]; - - Habit fromHabit = Habit.getByPosition(from); - Habit toHabit = Habit.getByPosition(to); - Habit.reorder(fromHabit, toHabit); - - int actualPositions[] = new int[n]; - - for (int j = 0; j < n; j++) - { - Habit h = Habit.get(ids.get(j)); - if (h == null) fail(); - actualPositions[j] = h.position; - } - - assertThat(actualPositions, equalTo(expectedPosition[i])); - } - } - - @Test - public void test_rebuildOrder() - { - List ids = new LinkedList<>(); - int originalPositions[] = { 0, 1, 1, 4, 6, 8, 10, 10, 13}; - - for (int p : originalPositions) - { - Habit h = new Habit(); - h.position = p; - h.save(); - ids.add(h.getId()); - } - - Habit.rebuildOrder(); - - for (int i = 0; i < originalPositions.length; i++) - { - Habit h = Habit.get(ids.get(i)); - if(h == null) fail(); - assertThat(h.position, is(i)); - } - } - - @Test - public void test_getHabitsWithReminder() - { - List habitsWithReminder = new LinkedList<>(); - - for(int i = 0; i < 10; i++) - { - Habit habit = new Habit(); - if(i % 2 == 0) - { - habit.reminderDays = DateUtils.ALL_WEEK_DAYS; - habit.reminderHour = 8; - habit.reminderMin = 30; - habitsWithReminder.add(habit); - } - habit.save(); - } - - assertThat(habitsWithReminder, equalTo(Habit.getHabitsWithReminder())); - } - - @Test - public void test_archive_unarchive() - { - List allHabits = new LinkedList<>(); - List archivedHabits = new LinkedList<>(); - List unarchivedHabits = new LinkedList<>(); - - for(int i = 0; i < 10; i++) - { - Habit habit = new Habit(); - habit.save(); - allHabits.add(habit); - - if(i % 2 == 0) - archivedHabits.add(habit); - else - unarchivedHabits.add(habit); - } - - Habit.archive(archivedHabits); - assertThat(Habit.getAll(false), equalTo(unarchivedHabits)); - assertThat(Habit.getAll(true), equalTo(allHabits)); - - Habit.unarchive(archivedHabits); - assertThat(Habit.getAll(false), equalTo(allHabits)); - assertThat(Habit.getAll(true), equalTo(allHabits)); - } - - @Test - public void test_setColor() - { - List habits = new LinkedList<>(); - - for(int i = 0; i < 10; i++) - { - Habit habit = new Habit(); - habit.color = i; - habit.save(); - habits.add(habit); - } - - int newColor = 100; - Habit.setColor(habits, newColor); - - for(Habit h : habits) - assertThat(h.color, equalTo(newColor)); - } - - @Test - public void test_hasReminder_clearReminder() - { - Habit h = new Habit(); - assertThat(h.hasReminder(), is(false)); - - h.reminderDays = DateUtils.ALL_WEEK_DAYS; - h.reminderHour = 8; - h.reminderMin = 30; - assertThat(h.hasReminder(), is(true)); - - h.clearReminder(); - assertThat(h.hasReminder(), is(false)); - } - - @Test - public void test_writeCSV() throws IOException - { - HabitFixtures.createEmptyHabit(); - HabitFixtures.createShortHabit(); - - String expectedCSV = - "Position,Name,Description,NumRepetitions,Interval,Color\n" + - "001,Meditate,Did you meditate this morning?,1,1,#AFB42B\n" + - "002,Wake up early,Did you wake up before 6am?,2,3,#00897B\n"; - - StringWriter writer = new StringWriter(); - Habit.writeCSV(Habit.getAll(true), writer); - - MatcherAssert.assertThat(writer.toString(), equalTo(expectedCSV)); - } -} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java deleted file mode 100644 index 5cfbf95e5..000000000 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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.unit.models; - -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; - -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Repetition; -import org.isoron.uhabits.unit.HabitFixtures; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Arrays; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Random; - -import static junit.framework.Assert.assertFalse; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class RepetitionListTest extends BaseAndroidTest -{ - private Habit habit; - private Habit emptyHabit; - - @Before - public void setUp() - { - super.setUp(); - - HabitFixtures.purgeHabits(); - habit = HabitFixtures.createShortHabit(); - emptyHabit = HabitFixtures.createEmptyHabit(); - } - - @After - public void tearDown() - { - DateUtils.setFixedLocalTime(null); - } - - @Test - public void test_contains() - { - long current = DateUtils.getStartOfToday(); - - for(boolean b : HabitFixtures.NON_DAILY_HABIT_CHECKS) - { - assertThat(habit.repetitions.contains(current), equalTo(b)); - current -= DateUtils.millisecondsInOneDay; - } - - for(int i = 0; i < 3; i++) - { - assertThat(habit.repetitions.contains(current), equalTo(false)); - current -= DateUtils.millisecondsInOneDay; - } - } - - @Test - public void test_delete() - { - long timestamp = DateUtils.getStartOfToday(); - assertThat(habit.repetitions.contains(timestamp), equalTo(true)); - - habit.repetitions.delete(timestamp); - assertThat(habit.repetitions.contains(timestamp), equalTo(false)); - } - - @Test - public void test_toggle() - { - long timestamp = DateUtils.getStartOfToday(); - assertThat(habit.repetitions.contains(timestamp), equalTo(true)); - - habit.repetitions.toggle(timestamp); - assertThat(habit.repetitions.contains(timestamp), equalTo(false)); - - habit.repetitions.toggle(timestamp); - assertThat(habit.repetitions.contains(timestamp), equalTo(true)); - } - - @Test - public void test_getWeekDayFrequency() - { - Random random = new Random(); - Integer weekdayCount[][] = new Integer[12][7]; - Integer monthCount[] = new Integer[12]; - - Arrays.fill(monthCount, 0); - for(Integer row[] : weekdayCount) - Arrays.fill(row, 0); - - GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); - - // Sets the current date to the end of November - day.set(2015, 10, 30); - DateUtils.setFixedLocalTime(day.getTimeInMillis()); - - // Add repetitions randomly from January to December - // Leaves the month of March empty, to check that it returns null - day.set(2015, 0, 1); - for(int i = 0; i < 365; i ++) - { - if(random.nextBoolean()) - { - int month = day.get(Calendar.MONTH); - int week = day.get(Calendar.DAY_OF_WEEK) % 7; - - if(month != 2) - { - if (month <= 10) - { - weekdayCount[month][week]++; - monthCount[month]++; - } - emptyHabit.repetitions.toggle(day.getTimeInMillis()); - } - } - - day.add(Calendar.DAY_OF_YEAR, 1); - } - - HashMap freq = emptyHabit.repetitions.getWeekdayFrequency(); - - // Repetitions until November should be counted correctly - for(int month = 0; month < 11; month++) - { - day.set(2015, month, 1); - Integer actualCount[] = freq.get(day.getTimeInMillis()); - if(monthCount[month] == 0) - assertThat(actualCount, equalTo(null)); - else - assertThat(actualCount, equalTo(weekdayCount[month])); - } - - // Repetitions in December should be discarded - day.set(2015, 11, 1); - assertThat(freq.get(day.getTimeInMillis()), equalTo(null)); - } - - @Test - public void test_count() - { - long to = DateUtils.getStartOfToday(); - long from = to - 9 * DateUtils.millisecondsInOneDay; - assertThat(habit.repetitions.count(from, to), equalTo(6)); - - to = DateUtils.getStartOfToday() - DateUtils.millisecondsInOneDay; - from = to - 5 * DateUtils.millisecondsInOneDay; - assertThat(habit.repetitions.count(from, to), equalTo(3)); - } - - @Test - public void test_getOldest() - { - long expectedOldestTimestamp = DateUtils.getStartOfToday() - 9 * DateUtils.millisecondsInOneDay; - - assertThat(habit.repetitions.getOldestTimestamp(), equalTo(expectedOldestTimestamp)); - - Repetition oldest = habit.repetitions.getOldest(); - assertFalse(oldest == null); - assertThat(oldest.timestamp, equalTo(expectedOldestTimestamp)); - } -} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java index 7ab145a37..fdda41504 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java @@ -23,11 +23,9 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.unit.HabitFixtures; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.DateUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -50,8 +48,8 @@ public class ScoreListTest extends BaseAndroidTest { super.setUp(); - HabitFixtures.purgeHabits(); - habit = HabitFixtures.createEmptyHabit(); + habitFixtures.purgeHabits(habitList); + habit = habitFixtures.createEmptyHabit(); } @After @@ -61,38 +59,53 @@ public class ScoreListTest extends BaseAndroidTest } @Test - public void test_invalidateNewerThan() + public void test_getAllValues_withGroups() { - assertThat(habit.scores.getTodayValue(), equalTo(0)); - - toggleRepetitions(0, 2); - assertThat(habit.scores.getTodayValue(), equalTo(1948077)); + toggleRepetitions(0, 20); - habit.freqNum = 1; - habit.freqDen = 2; - habit.scores.invalidateNewerThan(0); + int expectedValues[] = {11434978, 7894999, 3212362}; - assertThat(habit.scores.getTodayValue(), equalTo(1974654)); + int actualValues[] = habit.getScores().getAllValues(7); + assertThat(actualValues, equalTo(expectedValues)); } @Test - public void test_getTodayStarValue() + public void test_getAllValues_withoutGroups() { - assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.EMPTY_STAR)); - - int k = 0; - while(habit.scores.getTodayValue() < Score.HALF_STAR_CUTOFF) toggleRepetitions(k, ++k); - assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.HALF_STAR)); + toggleRepetitions(0, 20); - while(habit.scores.getTodayValue() < Score.FULL_STAR_CUTOFF) toggleRepetitions(k, ++k); - assertThat(habit.scores.getTodayStarStatus(), equalTo(Score.FULL_STAR)); + int expectedValues[] = { + 12629351, + 12266245, + 11883254, + 11479288, + 11053198, + 10603773, + 10129735, + 9629735, + 9102352, + 8546087, + 7959357, + 7340494, + 6687738, + 5999234, + 5273023, + 4507040, + 3699107, + 2846927, + 1948077, + 1000000 + }; + + int actualValues[] = habit.getScores().getAllValues(1); + assertThat(actualValues, equalTo(expectedValues)); } @Test public void test_getTodayValue() { toggleRepetitions(0, 20); - assertThat(habit.scores.getTodayValue(), equalTo(12629351)); + assertThat(habit.getScores().getTodayValue(), equalTo(12629351)); } @Test @@ -100,77 +113,84 @@ public class ScoreListTest extends BaseAndroidTest { toggleRepetitions(0, 20); - int expectedValues[] = { 12629351, 12266245, 11883254, 11479288, 11053198, 10603773, - 10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023, - 4507040, 3699107, 2846927, 1948077, 1000000 }; + int expectedValues[] = { + 12629351, + 12266245, + 11883254, + 11479288, + 11053198, + 10603773, + 10129735, + 9629735, + 9102352, + 8546087, + 7959357, + 7340494, + 6687738, + 5999234, + 5273023, + 4507040, + 3699107, + 2846927, + 1948077, + 1000000 + }; long current = DateUtils.getStartOfToday(); - for(int expectedValue : expectedValues) + for (int expectedValue : expectedValues) { - assertThat(habit.scores.getValue(current), equalTo(expectedValue)); + assertThat(habit.getScores().getValue(current), + equalTo(expectedValue)); current -= DateUtils.millisecondsInOneDay; } } @Test - public void test_getAllValues_withoutGroups() + public void test_invalidateNewerThan() { - toggleRepetitions(0, 20); - - int expectedValues[] = { 12629351, 12266245, 11883254, 11479288, 11053198, 10603773, - 10129735, 9629735, 9102352, 8546087, 7959357, 7340494, 6687738, 5999234, 5273023, - 4507040, 3699107, 2846927, 1948077, 1000000 }; - - int actualValues[] = habit.scores.getAllValues(1); - assertThat(actualValues, equalTo(expectedValues)); - } + assertThat(habit.getScores().getTodayValue(), equalTo(0)); - @Test - public void test_getAllValues_withGroups() - { - toggleRepetitions(0, 20); + toggleRepetitions(0, 2); + assertThat(habit.getScores().getTodayValue(), equalTo(1948077)); - int expectedValues[] = { 11434978, 7894999, 3212362 }; + habit.setFreqNum(1); + habit.setFreqDen(2); + habit.getScores().invalidateNewerThan(0); - int actualValues[] = habit.scores.getAllValues(7); - assertThat(actualValues, equalTo(expectedValues)); + assertThat(habit.getScores().getTodayValue(), equalTo(1974654)); } @Test public void test_writeCSV() throws IOException { - HabitFixtures.purgeHabits(); - Habit habit = HabitFixtures.createShortHabit(); - - String expectedCSV = - "2015-01-16,0.0519\n" + - "2015-01-17,0.1021\n" + - "2015-01-18,0.0986\n" + - "2015-01-19,0.0952\n" + - "2015-01-20,0.1439\n" + - "2015-01-21,0.1909\n" + - "2015-01-22,0.2364\n" + - "2015-01-23,0.2283\n" + - "2015-01-24,0.2205\n" + - "2015-01-25,0.2649\n"; + habitFixtures.purgeHabits(habitList); + Habit habit = habitFixtures.createShortHabit(); + + String expectedCSV = "2015-01-16,0.0519\n" + + "2015-01-17,0.1021\n" + + "2015-01-18,0.0986\n" + + "2015-01-19,0.0952\n" + + "2015-01-20,0.1439\n" + + "2015-01-21,0.1909\n" + + "2015-01-22,0.2364\n" + + "2015-01-23,0.2283\n" + + "2015-01-24,0.2205\n" + + "2015-01-25,0.2649\n"; StringWriter writer = new StringWriter(); - habit.scores.writeCSV(writer); + habit.getScores().writeCSV(writer); assertThat(writer.toString(), equalTo(expectedCSV)); } private void toggleRepetitions(final int from, final int to) { - DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() - { - @Override - public void execute() - { - long today = DateUtils.getStartOfToday(); - for (int i = from; i < to; i++) - habit.repetitions.toggle(today - i * DateUtils.millisecondsInOneDay); - } + DatabaseUtils.executeAsTransaction(() -> { + long today = DateUtils.getStartOfToday(); + for (int i = from; i < to; i++) + habit + .getRepetitions() + .toggleTimestamp(today - i * DateUtils.millisecondsInOneDay); }); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java index 16f6a5297..bc0a31c31 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java @@ -36,6 +36,7 @@ import static org.junit.Assert.assertThat; @SmallTest public class ScoreTest extends BaseAndroidTest { + @Override @Before public void setUp() { @@ -61,48 +62,24 @@ public class ScoreTest extends BaseAndroidTest assertThat(Score.compute(1, 0, checkmark), equalTo(1000000)); assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387)); assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775)); - assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE)); + assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo( + Score.MAX_VALUE)); } @Test public void test_compute_withNonDailyHabit() { int checkmark = Checkmark.CHECKED_EXPLICITLY; - assertThat(Score.compute(1/3.0, 0, checkmark), equalTo(1000000)); - assertThat(Score.compute(1/3.0, 5000000, checkmark), equalTo(5916180)); - assertThat(Score.compute(1/3.0, 10000000, checkmark), equalTo(10832360)); - assertThat(Score.compute(1/3.0, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE)); - - assertThat(Score.compute(1/7.0, 0, checkmark), equalTo(1000000)); - assertThat(Score.compute(1/7.0, 5000000, checkmark), equalTo(5964398)); - assertThat(Score.compute(1/7.0, 10000000, checkmark), equalTo(10928796)); - assertThat(Score.compute(1/7.0, Score.MAX_VALUE, checkmark), equalTo(Score.MAX_VALUE)); - } - - @Test - public void test_getStarStatus() - { - Score s = new Score(); - - s.score = Score.FULL_STAR_CUTOFF + 1; - assertThat(s.getStarStatus(), equalTo(Score.FULL_STAR)); - - s.score = Score.FULL_STAR_CUTOFF; - assertThat(s.getStarStatus(), equalTo(Score.FULL_STAR)); - - s.score = Score.FULL_STAR_CUTOFF - 1; - assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR)); - - s.score = Score.HALF_STAR_CUTOFF + 1; - assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR)); - - s.score = Score.HALF_STAR_CUTOFF; - assertThat(s.getStarStatus(), equalTo(Score.HALF_STAR)); - - s.score = Score.HALF_STAR_CUTOFF - 1; - assertThat(s.getStarStatus(), equalTo(Score.EMPTY_STAR)); - - s.score = 0; - assertThat(s.getStarStatus(), equalTo(Score.EMPTY_STAR)); + assertThat(Score.compute(1 / 3.0, 0, checkmark), equalTo(1000000)); + assertThat(Score.compute(1 / 3.0, 5000000, checkmark), equalTo(5916180)); + assertThat(Score.compute(1 / 3.0, 10000000, checkmark), equalTo(10832360)); + assertThat(Score.compute(1 / 3.0, Score.MAX_VALUE, checkmark), equalTo( + Score.MAX_VALUE)); + + assertThat(Score.compute(1 / 7.0, 0, checkmark), equalTo(1000000)); + assertThat(Score.compute(1 / 7.0, 5000000, checkmark), equalTo(5964398)); + assertThat(Score.compute(1 / 7.0, 10000000, checkmark), equalTo(10928796)); + assertThat(Score.compute(1 / 7.0, Score.MAX_VALUE, checkmark), equalTo( + Score.MAX_VALUE)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java index 189dfdc96..51cb53dae 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java @@ -52,21 +52,16 @@ public class ExportCSVTaskTest extends BaseAndroidTest @Test public void testExportCSV() throws Throwable { - HabitFixtures.createShortHabit(); - List habits = Habit.getAll(true); + habitFixtures.createShortHabit(); + List habits = habitList.getAll(true); ExportCSVTask task = new ExportCSVTask(habits, null); - task.setListener(new ExportCSVTask.Listener() - { - @Override - public void onExportCSVFinished(String archiveFilename) - { - assertThat(archiveFilename, is(not(nullValue()))); + task.setListener(archiveFilename -> { + assertThat(archiveFilename, is(not(nullValue()))); - File f = new File(archiveFilename); - assertTrue(f.exists()); - assertTrue(f.canRead()); - } + File f = new File(archiveFilename); + assertTrue(f.exists()); + assertTrue(f.canRead()); }); task.execute(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java index 599a8e5f2..1a99e87df 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java @@ -40,8 +40,10 @@ public class CheckmarkButtonViewTest extends ViewTest public static final String PATH = "ui/habits/list/CheckmarkButtonView/"; private CountDownLatch latch; + private CheckmarkButtonView view; + @Override @Before public void setUp() { @@ -51,24 +53,23 @@ public class CheckmarkButtonViewTest extends ViewTest latch = new CountDownLatch(1); view = new CheckmarkButtonView(targetContext); view.setValue(Checkmark.UNCHECKED); - view.setColor(ColorUtils.CSV_PALETTE[7]); + view.setColor(ColorUtils.getAndroidTestColor(7)); measureView(dpToPixels(40), dpToPixels(40), view); } - protected void assertRendersCheckedExplicitly() throws IOException - { - assertRenders(view, PATH + "render_explicit_check.png"); - } - - protected void assertRendersUnchecked() throws IOException + @Test + public void testRender_explicitCheck() throws Exception { - assertRenders(view, PATH + "render_unchecked.png"); + view.setValue(Checkmark.CHECKED_EXPLICITLY); + assertRendersCheckedExplicitly(); } - protected void assertRendersCheckedImplicitly() throws IOException + @Test + public void testRender_implicitCheck() throws Exception { - assertRenders(view, PATH + "render_implicit_check.png"); + view.setValue(Checkmark.CHECKED_IMPLICITLY); + assertRendersCheckedImplicitly(); } @Test @@ -78,18 +79,19 @@ public class CheckmarkButtonViewTest extends ViewTest assertRendersUnchecked(); } - @Test - public void testRender_explicitCheck() throws Exception + protected void assertRendersCheckedExplicitly() throws IOException { - view.setValue(Checkmark.CHECKED_EXPLICITLY); - assertRendersCheckedExplicitly(); + assertRenders(view, PATH + "render_explicit_check.png"); } - @Test - public void testRender_implicitCheck() throws Exception + protected void assertRendersCheckedImplicitly() throws IOException { - view.setValue(Checkmark.CHECKED_IMPLICITLY); - assertRendersCheckedImplicitly(); + assertRenders(view, PATH + "render_implicit_check.png"); + } + + protected void assertRendersUnchecked() throws IOException + { + assertRenders(view, PATH + "render_unchecked.png"); } // @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java index 1f6edd1c4..bd57a3818 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java @@ -54,13 +54,14 @@ public class CheckmarkPanelViewTest extends ViewTest Habit habit = new Habit(); latch = new CountDownLatch(1); - checkmarks = new int[]{Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED, + checkmarks = new int[]{ + Checkmark.CHECKED_EXPLICITLY, Checkmark.UNCHECKED, Checkmark.CHECKED_IMPLICITLY, Checkmark.CHECKED_EXPLICITLY}; view = new CheckmarkPanelView(targetContext); view.setHabit(habit); view.setCheckmarkValues(checkmarks); - view.setColor(ColorUtils.CSV_PALETTE[7]); + view.setColor(ColorUtils.getAndroidTestColor(7)); measureView(dpToPixels(200), dpToPixels(200), view); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java index 5cde3d739..a5eb79627 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java @@ -23,11 +23,10 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; -import org.isoron.uhabits.views.CheckmarkWidgetView; +import org.isoron.uhabits.widgets.views.CheckmarkWidgetView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,6 +38,7 @@ import java.io.IOException; public class CheckmarkWidgetViewTest extends ViewTest { private CheckmarkWidgetView view; + private Habit habit; @Before @@ -47,7 +47,7 @@ public class CheckmarkWidgetViewTest extends ViewTest super.setUp(); InterfaceUtils.setFixedTheme(R.style.TransparentWidgetTheme); - habit = HabitFixtures.createShortHabit(); + habit = habitFixtures.createShortHabit(); view = new CheckmarkWidgetView(targetContext); view.setHabit(habit); refreshData(view); @@ -60,23 +60,14 @@ public class CheckmarkWidgetViewTest extends ViewTest assertRenders(view, "CheckmarkView/checked.png"); } - @Test - public void testRender_unchecked() throws IOException - { - habit.repetitions.toggle(DateUtils.getStartOfToday()); - view.refreshData(); - - assertRenders(view, "CheckmarkView/unchecked.png"); - } - @Test public void testRender_implicitlyChecked() throws IOException { long today = DateUtils.getStartOfToday(); long day = DateUtils.millisecondsInOneDay; - habit.repetitions.toggle(today); - habit.repetitions.toggle(today - day); - habit.repetitions.toggle(today - 2 * day); + habit.getRepetitions().toggleTimestamp(today); + habit.getRepetitions().toggleTimestamp(today - day); + habit.getRepetitions().toggleTimestamp(today - 2 * day); view.refreshData(); assertRenders(view, "CheckmarkView/implicitly_checked.png"); @@ -88,4 +79,13 @@ public class CheckmarkWidgetViewTest extends ViewTest measureView(dpToPixels(300), dpToPixels(300), view); assertRenders(view, "CheckmarkView/large_size.png"); } + + @Test + public void testRender_unchecked() throws IOException + { + habit.getRepetitions().toggleTimestamp(DateUtils.getStartOfToday()); + view.refreshData(); + + assertRenders(view, "CheckmarkView/unchecked.png"); + } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java index b99cb4c89..f43578699 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java @@ -23,8 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; -import org.isoron.uhabits.views.HabitFrequencyView; +import org.isoron.uhabits.ui.habits.show.views.HabitFrequencyView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,8 +39,8 @@ public class HabitFrequencyViewTest extends ViewTest { super.setUp(); - HabitFixtures.purgeHabits(); - Habit habit = HabitFixtures.createLongHabit(); + habitFixtures.purgeHabits(habitList); + Habit habit = habitFixtures.createLongHabit(); view = new HabitFrequencyView(targetContext); view.setHabit(habit); @@ -56,10 +55,12 @@ public class HabitFrequencyViewTest extends ViewTest } @Test - public void testRender_withTransparentBackground() throws Throwable + public void testRender_withDataOffset() throws Throwable { - view.setIsBackgroundTransparent(true); - assertRenders(view, "HabitFrequencyView/renderTransparent.png"); + view.onScroll(null, null, -dpToPixels(150), 0); + view.invalidate(); + + assertRenders(view, "HabitFrequencyView/renderDataOffset.png"); } @Test @@ -70,11 +71,9 @@ public class HabitFrequencyViewTest extends ViewTest } @Test - public void testRender_withDataOffset() throws Throwable + public void testRender_withTransparentBackground() throws Throwable { - view.onScroll(null, null, -dpToPixels(150), 0); - view.invalidate(); - - assertRenders(view, "HabitFrequencyView/renderDataOffset.png"); + view.setIsBackgroundTransparent(true); + assertRenders(view, "HabitFrequencyView/renderTransparent.png"); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java index 7bf667491..f2dcb0721 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java @@ -22,10 +22,9 @@ package org.isoron.uhabits.unit.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; -import org.isoron.uhabits.views.HabitHistoryView; +import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +39,7 @@ import static org.hamcrest.Matchers.equalTo; public class HabitHistoryViewTest extends ViewTest { private Habit habit; + private HabitHistoryView view; @Before @@ -47,8 +47,8 @@ public class HabitHistoryViewTest extends ViewTest { super.setUp(); - HabitFixtures.purgeHabits(); - habit = HabitFixtures.createLongHabit(); + habitFixtures.purgeHabits(habitList); + habit = habitFixtures.createLongHabit(); view = new HabitHistoryView(targetContext); view.setHabit(habit); @@ -57,69 +57,69 @@ public class HabitHistoryViewTest extends ViewTest } @Test - public void testRender() throws Throwable + public void tapDate_atInvalidLocations() throws Throwable { - assertRenders(view, "HabitHistoryView/render.png"); - } + int expectedCheckmarkValues[] = habit.getCheckmarks().getAllValues(); - @Test - public void testRender_withTransparentBackground() throws Throwable - { - view.setIsBackgroundTransparent(true); - assertRenders(view, "HabitHistoryView/renderTransparent.png"); - } + view.setIsEditable(true); + tap(view, 118, 13); // header + tap(view, 336, 60); // tomorrow's square + tap(view, 370, 60); // right axis + waitForAsyncTasks(); - @Test - public void testRender_withDifferentSize() throws Throwable - { - measureView(dpToPixels(200), dpToPixels(200), view); - assertRenders(view, "HabitHistoryView/renderDifferentSize.png"); + int actualCheckmarkValues[] = habit.getCheckmarks().getAllValues(); + assertThat(actualCheckmarkValues, equalTo(expectedCheckmarkValues)); } @Test - public void testRender_withDataOffset() throws Throwable + public void tapDate_withEditableView() throws Throwable { - view.onScroll(null, null, -dpToPixels(150), 0); - view.invalidate(); + view.setIsEditable(true); + tap(view, 340, 40); // today's square + waitForAsyncTasks(); - assertRenders(view, "HabitHistoryView/renderDataOffset.png"); + long today = DateUtils.getStartOfToday(); + assertFalse(habit.getRepetitions().containsTimestamp(today)); } @Test - public void tapDate_withEditableView() throws Throwable + public void tapDate_withReadOnlyView() throws Throwable { - view.setIsEditable(true); + view.setIsEditable(false); tap(view, 340, 40); // today's square waitForAsyncTasks(); long today = DateUtils.getStartOfToday(); - assertFalse(habit.repetitions.contains(today)); + assertTrue(habit.getRepetitions().containsTimestamp(today)); } @Test - public void tapDate_atInvalidLocations() throws Throwable + public void testRender() throws Throwable { - int expectedCheckmarkValues[] = habit.checkmarks.getAllValues(); + assertRenders(view, "HabitHistoryView/render.png"); + } - view.setIsEditable(true); - tap(view, 118, 13); // header - tap(view, 336, 60); // tomorrow's square - tap(view, 370, 60); // right axis - waitForAsyncTasks(); + @Test + public void testRender_withDataOffset() throws Throwable + { + view.onScroll(null, null, -dpToPixels(150), 0); + view.invalidate(); - int actualCheckmarkValues[] = habit.checkmarks.getAllValues(); - assertThat(actualCheckmarkValues, equalTo(expectedCheckmarkValues)); + assertRenders(view, "HabitHistoryView/renderDataOffset.png"); } @Test - public void tapDate_withReadOnlyView() throws Throwable + public void testRender_withDifferentSize() throws Throwable { - view.setIsEditable(false); - tap(view, 340, 40); // today's square - waitForAsyncTasks(); + measureView(dpToPixels(200), dpToPixels(200), view); + assertRenders(view, "HabitHistoryView/renderDifferentSize.png"); + } - long today = DateUtils.getStartOfToday(); - assertTrue(habit.repetitions.contains(today)); + @Test + public void testRender_withTransparentBackground() throws Throwable + { + view.setIsBackgroundTransparent(true); + assertRenders(view, "HabitHistoryView/renderTransparent.png"); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java index 1f5b13a2f..31c0d84a0 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java @@ -24,8 +24,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; -import org.isoron.uhabits.views.HabitScoreView; +import org.isoron.uhabits.ui.habits.show.views.HabitScoreView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,6 +34,7 @@ import org.junit.runner.RunWith; public class HabitScoreViewTest extends ViewTest { private Habit habit; + private HabitScoreView view; @Before @@ -42,8 +42,8 @@ public class HabitScoreViewTest extends ViewTest { super.setUp(); - HabitFixtures.purgeHabits(); - habit = HabitFixtures.createLongHabit(); + habitFixtures.purgeHabits(habitList); + habit = habitFixtures.createLongHabit(); view = new HabitScoreView(targetContext); view.setHabit(habit); @@ -55,15 +55,18 @@ public class HabitScoreViewTest extends ViewTest @Test public void testRender() throws Throwable { - Log.d("HabitScoreViewTest", String.format("height=%d", dpToPixels(100))); + Log.d("HabitScoreViewTest", + String.format("height=%d", dpToPixels(100))); assertRenders(view, "HabitScoreView/render.png"); } @Test - public void testRender_withTransparentBackground() throws Throwable + public void testRender_withDataOffset() throws Throwable { - view.setIsTransparencyEnabled(true); - assertRenders(view, "HabitScoreView/renderTransparent.png"); + view.onScroll(null, null, -dpToPixels(150), 0); + view.invalidate(); + + assertRenders(view, "HabitScoreView/renderDataOffset.png"); } @Test @@ -73,15 +76,6 @@ public class HabitScoreViewTest extends ViewTest assertRenders(view, "HabitScoreView/renderDifferentSize.png"); } - @Test - public void testRender_withDataOffset() throws Throwable - { - view.onScroll(null, null, -dpToPixels(150), 0); - view.invalidate(); - - assertRenders(view, "HabitScoreView/renderDataOffset.png"); - } - @Test public void testRender_withMonthlyBucket() throws Throwable { @@ -92,6 +86,13 @@ public class HabitScoreViewTest extends ViewTest assertRenders(view, "HabitScoreView/renderMonthly.png"); } + @Test + public void testRender_withTransparentBackground() throws Throwable + { + view.setIsTransparencyEnabled(true); + assertRenders(view, "HabitScoreView/renderTransparent.png"); + } + @Test public void testRender_withYearlyBucket() throws Throwable { diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java index 3b65c5a5d..9cb86086f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java @@ -23,8 +23,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; -import org.isoron.uhabits.views.HabitStreakView; +import org.isoron.uhabits.ui.habits.show.views.HabitStreakView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,13 +34,14 @@ public class HabitStreakViewTest extends ViewTest { private HabitStreakView view; + @Override @Before public void setUp() { super.setUp(); - HabitFixtures.purgeHabits(); - Habit habit = HabitFixtures.createLongHabit(); + habitFixtures.purgeHabits(habitList); + Habit habit = habitFixtures.createLongHabit(); view = new HabitStreakView(targetContext); measureView(dpToPixels(300), dpToPixels(100), view); @@ -56,13 +56,6 @@ public class HabitStreakViewTest extends ViewTest assertRenders(view, "HabitStreakView/render.png"); } - @Test - public void testRender_withTransparentBackground() throws Throwable - { - view.setIsBackgroundTransparent(true); - assertRenders(view, "HabitStreakView/renderTransparent.png"); - } - @Test public void testRender_withSmallSize() throws Throwable { @@ -71,4 +64,11 @@ public class HabitStreakViewTest extends ViewTest assertRenders(view, "HabitStreakView/renderSmallSize.png"); } + + @Test + public void testRender_withTransparentBackground() throws Throwable + { + view.setIsBackgroundTransparent(true); + assertRenders(view, "HabitStreakView/renderTransparent.png"); + } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java deleted file mode 100644 index ca61aac04..000000000 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/NumberViewTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.unit.views; - -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.views.NumberView; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class NumberViewTest extends ViewTest -{ - private NumberView view; - - @Before - public void setUp() - { - super.setUp(); - - view = new NumberView(targetContext); - view.setLabel("Hello world"); - view.setNumber(31); - view.setColor(ColorUtils.CSV_PALETTE[0]); - measureView(dpToPixels(100), dpToPixels(100), view); - } - - @Test - public void testRender_base() throws IOException - { - assertRenders(view, "NumberView/render.png"); - } - - @Test - public void testRender_withLongLabel() throws IOException - { - view.setLabel("The quick brown fox jumps over the lazy fox"); - - measureView(dpToPixels(100), dpToPixels(100), view); - assertRenders(view, "NumberView/renderLongLabel.png"); - } - - @Test - public void testRender_withDifferentParams() throws IOException - { - view.setNumber(500); - view.setColor(ColorUtils.CSV_PALETTE[5]); - view.setTextSize(targetContext.getResources().getDimension(R.dimen.tinyTextSize)); - - measureView(dpToPixels(200), dpToPixels(200), view); - assertRenders(view, "NumberView/renderDifferentParams.png"); - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java index 972fae3c8..0b6ba8c47 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java @@ -24,7 +24,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.views.RingView; +import org.isoron.uhabits.ui.habits.show.views.RingView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,7 +45,7 @@ public class RingViewTest extends ViewTest view = new RingView(targetContext); view.setPercentage(0.6f); view.setText("60%"); - view.setColor(ColorUtils.CSV_PALETTE[0]); + view.setColor(ColorUtils.getAndroidTestColor(0)); view.setBackgroundColor(Color.WHITE); view.setThickness(dpToPixels(3)); } @@ -61,7 +61,7 @@ public class RingViewTest extends ViewTest public void testRender_withDifferentParams() throws IOException { view.setPercentage(0.25f); - view.setColor(ColorUtils.CSV_PALETTE[5]); + view.setColor(ColorUtils.getAndroidTestColor(5)); measureView(dpToPixels(200), dpToPixels(200), view); assertRenders(view, "RingView/renderDifferentParams.png"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index 691e9d50e..1f71cea9a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -30,7 +30,7 @@ import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; import java.io.File; import java.io.FileOutputStream; diff --git a/app/src/main/java/org/isoron/uhabits/AndroidComponent.java b/app/src/main/java/org/isoron/uhabits/AndroidComponent.java index 45eb998d9..4a53960e0 100644 --- a/app/src/main/java/org/isoron/uhabits/AndroidComponent.java +++ b/app/src/main/java/org/isoron/uhabits/AndroidComponent.java @@ -23,6 +23,9 @@ import javax.inject.Singleton; import dagger.Component; +/** + * Dependency injection component for classes that are specific to Android. + */ @Singleton @Component(modules = {AndroidModule.class}) public interface AndroidComponent extends BaseComponent diff --git a/app/src/main/java/org/isoron/uhabits/AndroidModule.java b/app/src/main/java/org/isoron/uhabits/AndroidModule.java index 3bbc1d4d1..bd658b9f8 100644 --- a/app/src/main/java/org/isoron/uhabits/AndroidModule.java +++ b/app/src/main/java/org/isoron/uhabits/AndroidModule.java @@ -20,6 +20,10 @@ package org.isoron.uhabits; import org.isoron.uhabits.commands.CommandRunner; +import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.models.ModelFactory; +import org.isoron.uhabits.models.sqlite.SQLModelFactory; +import org.isoron.uhabits.models.sqlite.SQLiteHabitList; import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; import org.isoron.uhabits.utils.Preferences; @@ -28,27 +32,46 @@ import javax.inject.Singleton; import dagger.Module; import dagger.Provides; +/** + * Module that provides dependencies when the application is running on + * Android. + *

+ * This module is also used for instrumented tests. + */ @Module public class AndroidModule { @Provides @Singleton - Preferences providePreferences() + CommandRunner provideCommandRunner() { - return new Preferences(); + return new CommandRunner(); } @Provides @Singleton - CommandRunner provideCommandRunner() + HabitCardListCache provideHabitCardListCache() { - return new CommandRunner(); + return new HabitCardListCache(); } @Provides @Singleton - HabitCardListCache provideHabitCardListCache() + HabitList provideHabitList() { - return new HabitCardListCache(); + return SQLiteHabitList.getInstance(); + } + + @Provides + ModelFactory provideModelFactory() + { + return new SQLModelFactory(); + } + + @Provides + @Singleton + Preferences providePreferences() + { + return new Preferences(); } } diff --git a/app/src/main/java/org/isoron/uhabits/BaseComponent.java b/app/src/main/java/org/isoron/uhabits/BaseComponent.java index ea1b6cecb..2c6c550ea 100644 --- a/app/src/main/java/org/isoron/uhabits/BaseComponent.java +++ b/app/src/main/java/org/isoron/uhabits/BaseComponent.java @@ -19,16 +19,34 @@ package org.isoron.uhabits; +import org.isoron.uhabits.commands.ArchiveHabitsCommand; +import org.isoron.uhabits.commands.ChangeHabitColorCommand; +import org.isoron.uhabits.commands.CreateHabitCommand; +import org.isoron.uhabits.commands.DeleteHabitsCommand; +import org.isoron.uhabits.commands.EditHabitCommand; +import org.isoron.uhabits.commands.UnarchiveHabitsCommand; +import org.isoron.uhabits.io.AbstractImporter; +import org.isoron.uhabits.io.HabitsCSVExporter; +import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.ToggleRepetitionTask; +import org.isoron.uhabits.ui.BaseSystem; import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; +import org.isoron.uhabits.ui.habits.edit.HistoryEditorDialog; +import org.isoron.uhabits.ui.habits.list.ListHabitsActivity; +import org.isoron.uhabits.ui.habits.list.ListHabitsController; import org.isoron.uhabits.ui.habits.list.ListHabitsSelectionMenu; +import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; -import org.isoron.uhabits.ui.habits.list.ListHabitsController; -import org.isoron.uhabits.ui.habits.list.controllers.CheckmarkButtonController; import org.isoron.uhabits.ui.habits.list.model.HintList; import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView; +import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; +import org.isoron.uhabits.widgets.BaseWidgetProvider; +import org.isoron.uhabits.widgets.HabitPickerDialog; +/** + * Base component for dependency injection. + */ public interface BaseComponent { void inject(CheckmarkButtonController checkmarkButtonController); @@ -50,4 +68,36 @@ public interface BaseComponent void inject(HintList hintList); void inject(HabitCardListAdapter habitCardListAdapter); + + void inject(ArchiveHabitsCommand archiveHabitsCommand); + + void inject(ChangeHabitColorCommand changeHabitColorCommand); + + void inject(UnarchiveHabitsCommand unarchiveHabitsCommand); + + void inject(EditHabitCommand editHabitCommand); + + void inject(CreateHabitCommand createHabitCommand); + + void inject(HabitPickerDialog habitPickerDialog); + + void inject(BaseWidgetProvider baseWidgetProvider); + + void inject(ShowHabitActivity showHabitActivity); + + void inject(DeleteHabitsCommand deleteHabitsCommand); + + void inject(ListHabitsActivity listHabitsActivity); + + void inject(BaseSystem baseSystem); + + void inject(HistoryEditorDialog historyEditorDialog); + + void inject(HabitsApplication application); + + void inject(Habit habit); + + void inject(AbstractImporter abstractImporter); + + void inject(HabitsCSVExporter habitsCSVExporter); } diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java index d992bda62..561290636 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java @@ -40,6 +40,7 @@ import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; import org.isoron.uhabits.utils.DateUtils; @@ -50,12 +51,26 @@ import java.util.Date; import javax.inject.Inject; +/** + * The Android BroadacastReceiver for Loop Habit Tracker. + *

+ * Currently, all broadcast messages are received and processed by this class. + */ public class HabitBroadcastReceiver extends BroadcastReceiver { public static final String ACTION_CHECK = "org.isoron.uhabits.ACTION_CHECK"; - public static final String ACTION_DISMISS = "org.isoron.uhabits.ACTION_DISMISS"; - public static final String ACTION_SHOW_REMINDER = "org.isoron.uhabits.ACTION_SHOW_REMINDER"; - public static final String ACTION_SNOOZE = "org.isoron.uhabits.ACTION_SNOOZE"; + + public static final String ACTION_DISMISS = + "org.isoron.uhabits.ACTION_DISMISS"; + + public static final String ACTION_SHOW_REMINDER = + "org.isoron.uhabits.ACTION_SHOW_REMINDER"; + + public static final String ACTION_SNOOZE = + "org.isoron.uhabits.ACTION_SNOOZE"; + + @Inject + HabitList habitList; @Inject CommandRunner commandRunner; @@ -66,6 +81,68 @@ public class HabitBroadcastReceiver extends BroadcastReceiver HabitsApplication.getComponent().inject(this); } + public static PendingIntent buildCheckIntent(Context context, + Habit habit, + Long timestamp) + { + Uri data = habit.getUri(); + Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class); + checkIntent.setData(data); + checkIntent.setAction(ACTION_CHECK); + if (timestamp != null) checkIntent.putExtra("timestamp", timestamp); + return PendingIntent.getBroadcast(context, 0, checkIntent, + PendingIntent.FLAG_ONE_SHOT); + } + + public static PendingIntent buildDismissIntent(Context context) + { + Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class); + deleteIntent.setAction(ACTION_DISMISS); + return PendingIntent.getBroadcast(context, 0, deleteIntent, 0); + } + + public static PendingIntent buildSnoozeIntent(Context context, Habit habit) + { + Uri data = habit.getUri(); + Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class); + snoozeIntent.setData(data); + snoozeIntent.setAction(ACTION_SNOOZE); + return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0); + } + + public static PendingIntent buildViewHabitIntent(Context context, + Habit habit) + { + Intent intent = new Intent(context, ShowHabitActivity.class); + intent.setData( + Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId())); + + return TaskStackBuilder + .create(context.getApplicationContext()) + .addNextIntentWithParentStack(intent) + .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + } + + public static void dismissNotification(Context context, Habit habit) + { + NotificationManager notificationManager = + (NotificationManager) context.getSystemService( + Activity.NOTIFICATION_SERVICE); + + int notificationId = (int) (habit.getId() % Integer.MAX_VALUE); + notificationManager.cancel(notificationId); + } + + public static void sendRefreshBroadcast(Context context) + { + LocalBroadcastManager manager = + LocalBroadcastManager.getInstance(context); + Intent refreshIntent = new Intent(HabitsApplication.ACTION_REFRESH); + manager.sendBroadcast(refreshIntent); + + WidgetManager.updateWidgets(context); + } + @Override public void onReceive(final Context context, Intent intent) { @@ -89,40 +166,23 @@ public class HabitBroadcastReceiver extends BroadcastReceiver break; case Intent.ACTION_BOOT_COMPLETED: - ReminderUtils.createReminderAlarms(context); + ReminderUtils.createReminderAlarms(context, habitList); break; } } - private void createReminderAlarmsDelayed(final Context context) - { - new Handler().postDelayed(() -> ReminderUtils.createReminderAlarms(context), 5000); - } - - private void snoozeHabit(Context context, Intent intent) - { - Uri data = intent.getData(); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - long delayMinutes = Long.parseLong(prefs.getString("pref_snooze_interval", "15")); - - long habitId = ContentUris.parseId(data); - Habit habit = Habit.get(habitId); - if(habit != null) - ReminderUtils.createReminderAlarm(context, habit, - new Date().getTime() + delayMinutes * 60 * 1000); - dismissNotification(context, habitId); - } - private void checkHabit(Context context, Intent intent) { Uri data = intent.getData(); - Long timestamp = intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); + Long timestamp = + intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); long habitId = ContentUris.parseId(data); - Habit habit = Habit.get(habitId); - if(habit != null) + Habit habit = habitList.getById(habitId); + if (habit != null) { - ToggleRepetitionCommand command = new ToggleRepetitionCommand(habit, timestamp); + ToggleRepetitionCommand command = + new ToggleRepetitionCommand(habit, timestamp); commandRunner.execute(command, habitId); } @@ -130,36 +190,26 @@ public class HabitBroadcastReceiver extends BroadcastReceiver sendRefreshBroadcast(context); } - public static void sendRefreshBroadcast(Context context) - { - LocalBroadcastManager manager = LocalBroadcastManager.getInstance(context); - Intent refreshIntent = new Intent(HabitsApplication.ACTION_REFRESH); - manager.sendBroadcast(refreshIntent); - - WidgetManager.updateWidgets(context); - } - - private void dismissAllHabits() + private boolean checkWeekday(Intent intent, Habit habit) { + Long timestamp = + intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); - } - - private void dismissNotification(Context context, Long habitId) - { - NotificationManager notificationManager = - (NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE); + boolean reminderDays[] = + DateUtils.unpackWeekdayList(habit.getReminderDays()); + int weekday = DateUtils.getWeekday(timestamp); - int notificationId = (int) (habitId % Integer.MAX_VALUE); - notificationManager.cancel(notificationId); + return reminderDays[weekday]; } - private void createNotification(final Context context, final Intent intent) { final Uri data = intent.getData(); - final Habit habit = Habit.get(ContentUris.parseId(data)); - final Long timestamp = intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); - final Long reminderTime = intent.getLongExtra("reminderTime", DateUtils.getStartOfToday()); + final Habit habit = habitList.getById(ContentUris.parseId(data)); + final Long timestamp = + intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); + final Long reminderTime = + intent.getLongExtra("reminderTime", DateUtils.getStartOfToday()); if (habit == null) return; @@ -170,7 +220,7 @@ public class HabitBroadcastReceiver extends BroadcastReceiver @Override protected void doInBackground() { - todayValue = habit.checkmarks.getTodayValue(); + todayValue = habit.getCheckmarks().getTodayValue(); } @Override @@ -183,41 +233,46 @@ public class HabitBroadcastReceiver extends BroadcastReceiver Intent contentIntent = new Intent(context, MainActivity.class); contentIntent.setData(data); PendingIntent contentPendingIntent = - PendingIntent.getActivity(context, 0, contentIntent, 0); + PendingIntent.getActivity(context, 0, contentIntent, 0); - PendingIntent dismissPendingIntent = buildDismissIntent(context); - PendingIntent checkIntentPending = buildCheckIntent(context, habit, timestamp); - PendingIntent snoozeIntentPending = buildSnoozeIntent(context, habit); + PendingIntent dismissPendingIntent = + buildDismissIntent(context); + PendingIntent checkIntentPending = + buildCheckIntent(context, habit, timestamp); + PendingIntent snoozeIntentPending = + buildSnoozeIntent(context, habit); Uri ringtoneUri = ReminderUtils.getRingtoneUri(context); NotificationCompat.WearableExtender wearableExtender = - new NotificationCompat.WearableExtender().setBackground( - BitmapFactory.decodeResource(context.getResources(), - R.drawable.stripe)); + new NotificationCompat.WearableExtender().setBackground( + BitmapFactory.decodeResource(context.getResources(), + R.drawable.stripe)); Notification notification = - new NotificationCompat.Builder(context) - .setSmallIcon(R.drawable.ic_notification) - .setContentTitle(habit.name) - .setContentText(habit.description) - .setContentIntent(contentPendingIntent) - .setDeleteIntent(dismissPendingIntent) - .addAction(R.drawable.ic_action_check, - context.getString(R.string.check), checkIntentPending) - .addAction(R.drawable.ic_action_snooze, - context.getString(R.string.snooze), snoozeIntentPending) - .setSound(ringtoneUri) - .extend(wearableExtender) - .setWhen(reminderTime) - .setShowWhen(true) - .build(); + new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle(habit.getName()) + .setContentText(habit.getDescription()) + .setContentIntent(contentPendingIntent) + .setDeleteIntent(dismissPendingIntent) + .addAction(R.drawable.ic_action_check, + context.getString(R.string.check), + checkIntentPending) + .addAction(R.drawable.ic_action_snooze, + context.getString(R.string.snooze), + snoozeIntentPending) + .setSound(ringtoneUri) + .extend(wearableExtender) + .setWhen(reminderTime) + .setShowWhen(true) + .build(); notification.flags |= Notification.FLAG_AUTO_CANCEL; NotificationManager notificationManager = - (NotificationManager) context.getSystemService( - Activity.NOTIFICATION_SERVICE); + (NotificationManager) context.getSystemService( + Activity.NOTIFICATION_SERVICE); int notificationId = (int) (habit.getId() % Integer.MAX_VALUE); notificationManager.notify(notificationId, notification); @@ -227,59 +282,39 @@ public class HabitBroadcastReceiver extends BroadcastReceiver }.execute(); } - public static PendingIntent buildSnoozeIntent(Context context, Habit habit) - { - Uri data = habit.getUri(); - Intent snoozeIntent = new Intent(context, HabitBroadcastReceiver.class); - snoozeIntent.setData(data); - snoozeIntent.setAction(ACTION_SNOOZE); - return PendingIntent.getBroadcast(context, 0, snoozeIntent, 0); - } - - public static PendingIntent buildCheckIntent(Context context, Habit habit, Long timestamp) - { - Uri data = habit.getUri(); - Intent checkIntent = new Intent(context, HabitBroadcastReceiver.class); - checkIntent.setData(data); - checkIntent.setAction(ACTION_CHECK); - if(timestamp != null) checkIntent.putExtra("timestamp", timestamp); - return PendingIntent.getBroadcast(context, 0, checkIntent, PendingIntent.FLAG_ONE_SHOT); - } - - public static PendingIntent buildDismissIntent(Context context) + private void createReminderAlarmsDelayed(final Context context) { - Intent deleteIntent = new Intent(context, HabitBroadcastReceiver.class); - deleteIntent.setAction(ACTION_DISMISS); - return PendingIntent.getBroadcast(context, 0, deleteIntent, 0); + new Handler().postDelayed( + () -> ReminderUtils.createReminderAlarms(context, habitList), 5000); } - public static PendingIntent buildViewHabitIntent(Context context, Habit habit) + private void dismissAllHabits() { - Intent intent = new Intent(context, ShowHabitActivity.class); - intent.setData(Uri.parse("content://org.isoron.uhabits/habit/" + habit.getId())); - return TaskStackBuilder.create(context.getApplicationContext()) - .addNextIntentWithParentStack(intent) - .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); } - private boolean checkWeekday(Intent intent, Habit habit) + private void dismissNotification(Context context, Long habitId) { - Long timestamp = intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); - - boolean reminderDays[] = DateUtils.unpackWeekdayList(habit.reminderDays); - int weekday = DateUtils.getWeekday(timestamp); + NotificationManager notificationManager = + (NotificationManager) context.getSystemService( + Activity.NOTIFICATION_SERVICE); - return reminderDays[weekday]; + int notificationId = (int) (habitId % Integer.MAX_VALUE); + notificationManager.cancel(notificationId); } - public static void dismissNotification(Context context, Habit habit) + private void snoozeHabit(Context context, Intent intent) { - NotificationManager notificationManager = - (NotificationManager) context.getSystemService( - Activity.NOTIFICATION_SERVICE); + Uri data = intent.getData(); + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(context); + long delayMinutes = + Long.parseLong(prefs.getString("pref_snooze_interval", "15")); - int notificationId = (int) (habit.getId() % Integer.MAX_VALUE); - notificationManager.cancel(notificationId); + long habitId = ContentUris.parseId(data); + Habit habit = habitList.getById(habitId); + if (habit != null) ReminderUtils.createReminderAlarm(context, habit, + new Date().getTime() + delayMinutes * 60 * 1000); + dismissNotification(context, habitId); } } diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index 19706124c..34f72d2fe 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -25,37 +25,53 @@ import android.support.annotation.Nullable; import com.activeandroid.ActiveAndroid; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.utils.DatabaseUtils; import java.io.File; +import javax.inject.Inject; + +/** + * The Android application for Loop Habit Tracker. + */ public class HabitsApplication extends Application { - public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH"; - public static final int RESULT_IMPORT_DATA = 1; + public static final String ACTION_REFRESH = + "org.isoron.uhabits.ACTION_REFRESH"; + + public static final int RESULT_BUG_REPORT = 4; + public static final int RESULT_EXPORT_CSV = 2; + public static final int RESULT_EXPORT_DB = 3; - public static final int RESULT_BUG_REPORT = 4; + + public static final int RESULT_IMPORT_DATA = 1; @Nullable private static HabitsApplication application; + private static BaseComponent component; + @Nullable private static Context context; - private static BaseComponent component; - public static boolean isTestMode() + @Inject + HabitList habitList; + + public static BaseComponent getComponent() { - try - { - if(context != null) - context.getClassLoader().loadClass("org.isoron.uhabits.unit.models.HabitTest"); - return true; - } - catch (final Exception e) - { - return false; - } + return component; + } + + public HabitList getHabitList() + { + return habitList; + } + + public static void setComponent(BaseComponent component) + { + HabitsApplication.component = component; } @Nullable @@ -70,14 +86,19 @@ public class HabitsApplication extends Application return application; } - public static BaseComponent getComponent() - { - return component; - } - - public static void setComponent(BaseComponent component) + public static boolean isTestMode() { - HabitsApplication.component = component; + try + { + if (context != null) context + .getClassLoader() + .loadClass("org.isoron.uhabits.unit.models.HabitTest"); + return true; + } + catch (final Exception e) + { + return false; + } } @Override @@ -91,9 +112,10 @@ public class HabitsApplication extends Application if (isTestMode()) { File db = DatabaseUtils.getDatabaseFile(); - if(db.exists()) db.delete(); + if (db.exists()) db.delete(); } + component.inject(this); DatabaseUtils.initializeActiveAndroid(); } diff --git a/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java b/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java index 6e619ef7a..f337949ea 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsBackupAgent.java @@ -23,6 +23,9 @@ import android.app.backup.BackupAgentHelper; import android.app.backup.FileBackupHelper; import android.app.backup.SharedPreferencesBackupHelper; +/** + * An Android BackupAgentHelper customized for this application. + */ public class HabitsBackupAgent extends BackupAgentHelper { @Override diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 0674a8035..ba7b6da06 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -21,6 +21,9 @@ package org.isoron.uhabits; import org.isoron.uhabits.ui.habits.list.ListHabitsActivity; +/** + * Application that starts upon clicking the launcher icon. + */ public class MainActivity extends ListHabitsActivity { /* diff --git a/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java index 25e998b7b..9e3238010 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java @@ -19,38 +19,52 @@ package org.isoron.uhabits.commands; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import java.util.List; +import javax.inject.Inject; + +/** + * Command to archive a list of habits. + */ public class ArchiveHabitsCommand extends Command { + @Inject + HabitList habitList; private List habits; public ArchiveHabitsCommand(List habits) { + HabitsApplication.getComponent().inject(this); this.habits = habits; } @Override public void execute() { - Habit.archive(habits); + for(Habit h : habits) h.setArchived(1); + habitList.update(habits); } @Override public void undo() { - Habit.unarchive(habits); + for(Habit h : habits) h.setArchived(0); + habitList.update(habits); } + @Override public Integer getExecuteStringId() { return R.string.toast_habit_archived; } + @Override public Integer getUndoStringId() { return R.string.toast_habit_unarchived; diff --git a/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java index ffcd6dfee..e09ec470b 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/ChangeHabitColorCommand.java @@ -19,60 +19,65 @@ package org.isoron.uhabits.commands; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + +/** + * Command to change the color of a list of habits. + */ public class ChangeHabitColorCommand extends Command { + @Inject + HabitList habitList; + List habits; + List originalColors; + Integer newColor; public ChangeHabitColorCommand(List habits, Integer newColor) { + HabitsApplication.getComponent().inject(this); + this.habits = habits; this.newColor = newColor; this.originalColors = new ArrayList<>(habits.size()); - for(Habit h : habits) - originalColors.add(h.color); + for (Habit h : habits) originalColors.add(h.getColor()); } @Override public void execute() { - Habit.setColor(habits, newColor); + for(Habit h : habits) h.setColor(newColor); + habitList.update(habits); } @Override - public void undo() - { - DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() - { - @Override - public void execute() - { - int k = 0; - for(Habit h : habits) - { - h.color = originalColors.get(k++); - h.save(); - } - } - }); - } - public Integer getExecuteStringId() { return R.string.toast_habit_changed; } + @Override public Integer getUndoStringId() { return R.string.toast_habit_changed; } + + @Override + public void undo() + { + int k = 0; + for (Habit h : habits) h.setColor(originalColors.get(k++)); + habitList.update(habits); + } } diff --git a/app/src/main/java/org/isoron/uhabits/commands/Command.java b/app/src/main/java/org/isoron/uhabits/commands/Command.java index b9427e38a..e319a5095 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/Command.java +++ b/app/src/main/java/org/isoron/uhabits/commands/Command.java @@ -19,12 +19,19 @@ package org.isoron.uhabits.commands; +/** + * A Command represents a desired set of changes that should be performed on the + * models. + *

+ * A command can be executed and undone. Each of these operations also provide + * an string that should be displayed to the user upon their completion. + *

+ * In general, commands should always be executed by a {@link CommandRunner}. + */ public abstract class Command { public abstract void execute(); - public abstract void undo(); - public Integer getExecuteStringId() { return null; @@ -34,4 +41,6 @@ public abstract class Command { return null; } + + public abstract void undo(); } diff --git a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java index 035e55087..d1051e744 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java +++ b/app/src/main/java/org/isoron/uhabits/commands/CommandRunner.java @@ -26,6 +26,12 @@ import org.isoron.uhabits.tasks.BaseTask; import java.util.LinkedList; +/** + * A CommandRunner executes and undoes commands. + *

+ * CommandRunners also allows objects to subscribe to it, and receive events + * whenever a command is performed. + */ public class CommandRunner { private LinkedList listeners; @@ -71,6 +77,10 @@ public class CommandRunner listeners.remove(l); } + /** + * Interface implemented by objects that want to receive an event whenever a + * command is executed. + */ public interface Listener { void onCommandExecuted(@NonNull Command command, diff --git a/app/src/main/java/org/isoron/uhabits/commands/CreateHabitCommand.java b/app/src/main/java/org/isoron/uhabits/commands/CreateHabitCommand.java index 7cc9ad51c..7668e5d2f 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/CreateHabitCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/CreateHabitCommand.java @@ -19,41 +19,48 @@ package org.isoron.uhabits.commands; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; +import javax.inject.Inject; + +/** + * Command to create a habit. + */ public class CreateHabitCommand extends Command { + @Inject + HabitList habitList; + private Habit model; private Long savedId; public CreateHabitCommand(Habit model) { this.model = model; + HabitsApplication.getComponent().inject(this); } @Override public void execute() { - Habit savedHabit = new Habit(model); - if (savedId == null) - { - savedHabit.save(); - savedId = savedHabit.getId(); - } - else - { - savedHabit.save(savedId); - } + Habit savedHabit = new Habit(); + savedHabit.copyFrom(model); + savedHabit.setId(savedId); + + habitList.add(savedHabit); + savedId = savedHabit.getId(); } @Override public void undo() { - Habit habit = Habit.get(savedId); + Habit habit = habitList.getById(savedId); if(habit == null) throw new RuntimeException("Habit not found"); - habit.cascadeDelete(); + habitList.remove(habit); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/commands/DeleteHabitsCommand.java b/app/src/main/java/org/isoron/uhabits/commands/DeleteHabitsCommand.java index 34e26c50c..0d800cd17 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/DeleteHabitsCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/DeleteHabitsCommand.java @@ -19,27 +19,36 @@ package org.isoron.uhabits.commands; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import java.util.List; +import javax.inject.Inject; + +/** + * Command to delete a list of habits. + */ public class DeleteHabitsCommand extends Command { + @Inject + HabitList habitList; + private List habits; public DeleteHabitsCommand(List habits) { this.habits = habits; + HabitsApplication.getComponent().inject(this); } @Override public void execute() { for(Habit h : habits) - h.cascadeDelete(); - - Habit.rebuildOrder(); + habitList.remove(h); } @Override @@ -48,11 +57,13 @@ public class DeleteHabitsCommand extends Command throw new UnsupportedOperationException(); } + @Override public Integer getExecuteStringId() { return R.string.toast_habit_deleted; } + @Override public Integer getUndoStringId() { return R.string.toast_habit_restored; diff --git a/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java b/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java index 7a7787d6a..90884e119 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java @@ -19,24 +19,43 @@ package org.isoron.uhabits.commands; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; +import javax.inject.Inject; + +/** + * Command to modify a habit. + */ public class EditHabitCommand extends Command { + @Inject + HabitList habitList; + private Habit original; + private Habit modified; + private long savedId; + private boolean hasIntervalChanged; public EditHabitCommand(Habit original, Habit modified) { + HabitsApplication.getComponent().inject(this); + this.savedId = original.getId(); - this.modified = new Habit(modified); - this.original = new Habit(original); + this.modified = new Habit(); + this.original = new Habit(); + + this.modified.copyFrom(modified); + this.original.copyFrom(original); - hasIntervalChanged = (!this.original.freqDen.equals(this.modified.freqDen) || - !this.original.freqNum.equals(this.modified.freqNum)); + hasIntervalChanged = + (!this.original.getFreqDen().equals(this.modified.getFreqDen()) || + !this.original.getFreqNum().equals(this.modified.getFreqNum())); } @Override @@ -45,6 +64,18 @@ public class EditHabitCommand extends Command copyAttributes(this.modified); } + @Override + public Integer getExecuteStringId() + { + return R.string.toast_habit_changed; + } + + @Override + public Integer getUndoStringId() + { + return R.string.toast_habit_changed_back; + } + @Override public void undo() { @@ -53,11 +84,11 @@ public class EditHabitCommand extends Command private void copyAttributes(Habit model) { - Habit habit = Habit.get(savedId); - if(habit == null) throw new RuntimeException("Habit not found"); + Habit habit = habitList.getById(savedId); + if (habit == null) throw new RuntimeException("Habit not found"); - habit.copyAttributes(model); - habit.save(); + habit.copyFrom(model); + habitList.update(habit); invalidateIfNeeded(habit); } @@ -66,19 +97,9 @@ public class EditHabitCommand extends Command { if (hasIntervalChanged) { - habit.checkmarks.deleteNewerThan(0); - habit.streaks.deleteNewerThan(0); - habit.scores.invalidateNewerThan(0); + habit.getCheckmarks().invalidateNewerThan(0); + habit.getStreaks().invalidateNewerThan(0); + habit.getScores().invalidateNewerThan(0); } } - - public Integer getExecuteStringId() - { - return R.string.toast_habit_changed; - } - - public Integer getUndoStringId() - { - return R.string.toast_habit_changed_back; - } } \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java index 451908433..8a3e981f8 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/ToggleRepetitionCommand.java @@ -21,6 +21,9 @@ package org.isoron.uhabits.commands; import org.isoron.uhabits.models.Habit; +/** + * Command to toggle a repetition. + */ public class ToggleRepetitionCommand extends Command { private Long offset; @@ -35,7 +38,7 @@ public class ToggleRepetitionCommand extends Command @Override public void execute() { - habit.repetitions.toggle(offset); + habit.getRepetitions().toggleTimestamp(offset); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java b/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java index 612481fa7..6f06e61eb 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java @@ -19,38 +19,52 @@ package org.isoron.uhabits.commands; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import java.util.List; +import javax.inject.Inject; + +/** + * Command to unarchive a list of habits. + */ public class UnarchiveHabitsCommand extends Command { + @Inject + HabitList habitList; private List habits; public UnarchiveHabitsCommand(List habits) { this.habits = habits; + HabitsApplication.getComponent().inject(this); } @Override public void execute() { - Habit.unarchive(habits); + for(Habit h : habits) h.setArchived(0); + habitList.update(habits); } @Override public void undo() { - Habit.archive(habits); + for(Habit h : habits) h.setArchived(1); + habitList.update(habits); } + @Override public Integer getExecuteStringId() { return R.string.toast_habit_unarchived; } + @Override public Integer getUndoStringId() { return R.string.toast_habit_archived; diff --git a/app/src/main/java/org/isoron/uhabits/commands/package-info.java b/app/src/main/java/org/isoron/uhabits/commands/package-info.java new file mode 100644 index 000000000..8fce85ae1 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/commands/package-info.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +/** + * Provides commands to modify the models, such as {@link + * org.isoron.uhabits.commands.CreateHabitCommand}. + */ +package org.isoron.uhabits.commands; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/io/AbstractImporter.java b/app/src/main/java/org/isoron/uhabits/io/AbstractImporter.java index 83cfddcb8..3f6da5f8b 100644 --- a/app/src/main/java/org/isoron/uhabits/io/AbstractImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/AbstractImporter.java @@ -21,13 +21,30 @@ package org.isoron.uhabits.io; import android.support.annotation.NonNull; +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.models.HabitList; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; +import javax.inject.Inject; + +/** + * AbstractImporter is the base class for all classes that import data from + * files into the app. + */ public abstract class AbstractImporter { + @Inject + HabitList habitList; + + public AbstractImporter() + { + HabitsApplication.getComponent().inject(this); + } + public abstract boolean canHandle(@NonNull File file) throws IOException; public abstract void importHabitsFromFile(@NonNull File file) throws IOException; diff --git a/app/src/main/java/org/isoron/uhabits/io/GenericImporter.java b/app/src/main/java/org/isoron/uhabits/io/GenericImporter.java index c08a3a72f..6f8e2ef70 100644 --- a/app/src/main/java/org/isoron/uhabits/io/GenericImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/GenericImporter.java @@ -26,6 +26,10 @@ import java.io.IOException; import java.util.LinkedList; import java.util.List; +/** + * A GenericImporter decides which implementation of AbstractImporter is able to + * handle a given file and delegates to it the task of importing the data. + */ public class GenericImporter extends AbstractImporter { List importers; @@ -42,8 +46,8 @@ public class GenericImporter extends AbstractImporter @Override public boolean canHandle(@NonNull File file) throws IOException { - for(AbstractImporter importer : importers) - if(importer.canHandle(file)) return true; + for (AbstractImporter importer : importers) + if (importer.canHandle(file)) return true; return false; } @@ -51,8 +55,7 @@ public class GenericImporter extends AbstractImporter @Override public void importHabitsFromFile(@NonNull File file) throws IOException { - for(AbstractImporter importer : importers) - if(importer.canHandle(file)) - importer.importHabitsFromFile(file); + for (AbstractImporter importer : importers) + if (importer.canHandle(file)) importer.importHabitsFromFile(file); } } diff --git a/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java b/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java index 5a1b6c514..0645fd00d 100644 --- a/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java @@ -24,8 +24,8 @@ import android.support.annotation.NonNull; import com.activeandroid.ActiveAndroid; import com.opencsv.CSVReader; -import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.DateUtils; import java.io.BufferedReader; import java.io.File; @@ -34,6 +34,9 @@ import java.io.IOException; import java.util.Calendar; import java.util.HashMap; +/** + * Class that imports data from HabitBull CSV files. + */ public class HabitBullCSVImporter extends AbstractImporter { @Override @@ -89,16 +92,16 @@ public class HabitBullCSVImporter extends AbstractImporter if(h == null) { h = new Habit(); - h.name = name; - h.description = description; - h.freqNum = h.freqDen = 1; - h.save(); - + h.setName(name); + h.setDescription(description); + h.setFreqDen(1); + h.setFreqNum(1); + habitList.add(h); habits.put(name, h); } - if(!h.repetitions.contains(timestamp)) - h.repetitions.toggle(timestamp); + if(!h.getRepetitions().containsTimestamp(timestamp)) + h.getRepetitions().toggleTimestamp(timestamp); } } } diff --git a/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java b/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java index 0a3a80e26..e53102f0a 100644 --- a/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/HabitsCSVExporter.java @@ -21,8 +21,10 @@ package org.isoron.uhabits.io; import android.support.annotation.NonNull; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.models.CheckmarkList; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.models.ScoreList; import org.isoron.uhabits.utils.DateUtils; @@ -37,6 +39,11 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import javax.inject.Inject; + +/** + * Class that exports the application data to CSV files. + */ public class HabitsCSVExporter { private List habits; @@ -46,8 +53,13 @@ public class HabitsCSVExporter private String exportDirName; + @Inject + HabitList habitList; + public HabitsCSVExporter(List habits, File dir) { + HabitsApplication.getComponent().inject(this); + this.habits = habits; this.exportDirName = dir.getAbsolutePath() + "/"; @@ -61,20 +73,20 @@ public class HabitsCSVExporter new File(exportDirName).mkdirs(); FileWriter out = new FileWriter(exportDirName + filename); generateFilenames.add(filename); - Habit.writeCSV(habits, out); + habitList.writeCSV(out); out.close(); for(Habit h : habits) { - String sane = sanitizeFilename(h.name); - String habitDirName = String.format("%03d %s", h.position + 1, sane); + String sane = sanitizeFilename(h.getName()); + String habitDirName = String.format("%03d %s", habitList.indexOf(h) + 1, sane); habitDirName = habitDirName.trim() + "/"; new File(exportDirName + habitDirName).mkdirs(); generateDirs.add(habitDirName); - writeScores(habitDirName, h.scores); - writeCheckmarks(habitDirName, h.checkmarks); + writeScores(habitDirName, h.getScores()); + writeCheckmarks(habitDirName, h.getCheckmarks()); } } diff --git a/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java index 6050a384e..d0037f9e3 100644 --- a/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/LoopDBImporter.java @@ -31,18 +31,22 @@ import org.isoron.uhabits.utils.FileUtils; import java.io.File; import java.io.IOException; +/** + * Class that imports data from database files exported by Loop Habit Tracker. + */ public class LoopDBImporter extends AbstractImporter { @Override public boolean canHandle(@NonNull File file) throws IOException { - if(!isSQLite3File(file)) return false; + if (!isSQLite3File(file)) return false; SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null, - SQLiteDatabase.OPEN_READONLY); + SQLiteDatabase.OPEN_READONLY); - Cursor c = db.rawQuery("select count(*) from SQLITE_MASTER where name=? or name=?", - new String[]{"Checkmarks", "Repetitions"}); + Cursor c = db.rawQuery( + "select count(*) from SQLITE_MASTER where name=? or name=?", + new String[]{"Checkmarks", "Repetitions"}); boolean result = (c.moveToFirst() && c.getInt(0) == 2); diff --git a/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java index a68d6c392..82d36282d 100644 --- a/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java @@ -23,14 +23,17 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; +import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; import java.io.File; import java.io.IOException; import java.util.GregorianCalendar; +/** + * Class that imports database files exported by Rewire. + */ public class RewireDBImporter extends AbstractImporter { @Override @@ -57,7 +60,7 @@ public class RewireDBImporter extends AbstractImporter final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null, SQLiteDatabase.OPEN_READONLY); - DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() + DatabaseUtils.executeAsTransaction(new DatabaseUtils.Callback() { @Override public void execute() @@ -91,30 +94,30 @@ public class RewireDBImporter extends AbstractImporter int periodIndex = c.getInt(7); Habit habit = new Habit(); - habit.name = name; - habit.description = description; + habit.setName(name); + habit.setDescription(description); int periods[] = { 7, 31, 365 }; switch (schedule) { case 0: - habit.freqNum = activeDays.split(",").length; - habit.freqDen = 7; + habit.setFreqNum(activeDays.split(",").length); + habit.setFreqDen(7); break; case 1: - habit.freqNum = days; - habit.freqDen = periods[periodIndex]; + habit.setFreqNum(days); + habit.setFreqDen(periods[periodIndex]); break; case 2: - habit.freqNum = 1; - habit.freqDen = repeatingCount; + habit.setFreqNum(1); + habit.setFreqDen(repeatingCount); break; } - habit.save(); + habitList.add(habit); createReminder(db, habit, id); createCheckmarks(db, habit, id); @@ -150,10 +153,10 @@ public class RewireDBImporter extends AbstractImporter reminderDays[idx] = true; } - habit.reminderDays = DateUtils.packWeekdayList(reminderDays); - habit.reminderHour = rewireReminder / 60; - habit.reminderMin = rewireReminder % 60; - habit.save(); + habit.setReminderDays(DateUtils.packWeekdayList(reminderDays)); + habit.setReminderHour(rewireReminder / 60); + habit.setReminderMin(rewireReminder % 60); + habitList.update(habit); } finally { @@ -161,7 +164,8 @@ public class RewireDBImporter extends AbstractImporter } } - private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull Habit habit, int rewireHabitId) + private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull + Habit habit, int rewireHabitId) { Cursor c = null; @@ -181,7 +185,7 @@ public class RewireDBImporter extends AbstractImporter GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); cal.set(year, month - 1, day); - habit.repetitions.toggle(cal.getTimeInMillis()); + habit.getRepetitions().toggleTimestamp(cal.getTimeInMillis()); } while (c.moveToNext()); } diff --git a/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java index 766af577a..c47c5580a 100644 --- a/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java @@ -23,26 +23,30 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; +import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.models.Habit; import java.io.File; import java.io.IOException; import java.util.GregorianCalendar; +/** + * Class that imports data from database files exported by Tickmate. + */ public class TickmateDBImporter extends AbstractImporter { @Override public boolean canHandle(@NonNull File file) throws IOException { - if(!isSQLite3File(file)) return false; + if (!isSQLite3File(file)) return false; SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null, - SQLiteDatabase.OPEN_READONLY); + SQLiteDatabase.OPEN_READONLY); - Cursor c = db.rawQuery("select count(*) from SQLITE_MASTER where name=? or name=?", - new String[]{"tracks", "track2groups"}); + Cursor c = db.rawQuery( + "select count(*) from SQLITE_MASTER where name=? or name=?", + new String[]{"tracks", "track2groups"}); boolean result = (c.moveToFirst() && c.getInt(0) == 2); @@ -54,47 +58,39 @@ public class TickmateDBImporter extends AbstractImporter @Override public void importHabitsFromFile(@NonNull File file) throws IOException { - final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null, + final SQLiteDatabase db = + SQLiteDatabase.openDatabase(file.getPath(), null, SQLiteDatabase.OPEN_READONLY); - DatabaseUtils.executeAsTransaction(new DatabaseUtils.Command() - { - @Override - public void execute() - { - createHabits(db); - } - }); - + DatabaseUtils.executeAsTransaction(() -> createHabits(db)); db.close(); } - private void createHabits(SQLiteDatabase db) + private void createCheckmarks(@NonNull SQLiteDatabase db, + @NonNull Habit habit, + int tickmateTrackId) { Cursor c = null; try { - c = db.rawQuery("select _id, name, description from tracks", new String[0]); + String[] params = {Integer.toString(tickmateTrackId)}; + c = db.rawQuery( + "select distinct year, month, day from ticks where _track_id=?", + params); if (!c.moveToFirst()) return; do { - int id = c.getInt(0); - String name = c.getString(1); - String description = c.getString(2); - - Habit habit = new Habit(); - habit.name = name; - habit.description = description; - habit.freqNum = 1; - habit.freqDen = 1; - habit.save(); + int year = c.getInt(0); + int month = c.getInt(1); + int day = c.getInt(2); - createCheckmarks(db, habit, id); + GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); + cal.set(year, month, day); - } - while (c.moveToNext()); + habit.getRepetitions().toggleTimestamp(cal.getTimeInMillis()); + } while (c.moveToNext()); } finally { @@ -102,28 +98,32 @@ public class TickmateDBImporter extends AbstractImporter } } - private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull Habit habit, int tickmateTrackId) + private void createHabits(SQLiteDatabase db) { Cursor c = null; try { - String[] params = { Integer.toString(tickmateTrackId) }; - c = db.rawQuery("select distinct year, month, day from ticks where _track_id=?", params); + c = db.rawQuery("select _id, name, description from tracks", + new String[0]); if (!c.moveToFirst()) return; do { - int year = c.getInt(0); - int month = c.getInt(1); - int day = c.getInt(2); + int id = c.getInt(0); + String name = c.getString(1); + String description = c.getString(2); - GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); - cal.set(year, month, day); + Habit habit = new Habit(); + habit.setName(name); + habit.setDescription(description); + habit.setFreqNum(1); + habit.setFreqDen(1); + habitList.add(habit); + + createCheckmarks(db, habit, id); - habit.repetitions.toggle(cal.getTimeInMillis()); - } - while (c.moveToNext()); + } while (c.moveToNext()); } finally { diff --git a/app/src/main/java/org/isoron/uhabits/io/package-info.java b/app/src/main/java/org/isoron/uhabits/io/package-info.java new file mode 100644 index 000000000..5cbd932fb --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/io/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides classes that deal with importing from and exporting to files. + */ +package org.isoron.uhabits.io; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java index a6c5ec06f..775b1b032 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java +++ b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java @@ -19,48 +19,65 @@ package org.isoron.uhabits.models; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; +import org.apache.commons.lang3.builder.ToStringBuilder; -@Table(name = "Checkmarks") -public class Checkmark extends Model +/** + * A Checkmark represents the completion status of the habit for a given day. + *

+ * While repetitions simply record that the habit was performed at a given date, + * a checkmark provides more information, such as whether a repetition was + * expected at that day or not. + *

+ * Checkmarks are computed automatically from the list of repetitions. + */ +public class Checkmark { /** - * Indicates that there was no repetition at the timestamp, even though a repetition was - * expected. + * Indicates that there was a repetition at the timestamp. */ - public static final int UNCHECKED = 0; + public static final int CHECKED_EXPLICITLY = 2; /** - * Indicates that there was no repetition at the timestamp, but one was not expected in any - * case, due to the frequency of the habit. + * Indicates that there was no repetition at the timestamp, but one was not + * expected in any case, due to the frequency of the habit. */ public static final int CHECKED_IMPLICITLY = 1; /** - * Indicates that there was a repetition at the timestamp. + * Indicates that there was no repetition at the timestamp, even though a + * repetition was expected. */ - public static final int CHECKED_EXPLICITLY = 2; + public static final int UNCHECKED = 0; - /** - * The habit to which this checkmark belongs. - */ - @Column(name = "habit") - public Habit habit; + final Habit habit; - /** - * Timestamp of the day to which this checkmark corresponds. Time of the day must be midnight - * (UTC). - */ - @Column(name = "timestamp") - public Long timestamp; + final long timestamp; - /** - * Indicates whether there is a repetition at the given timestamp or not, and whether the - * repetition was expected. Assumes one of the values UNCHECKED, CHECKED_EXPLICITLY or - * CHECKED_IMPLICITLY. - */ - @Column(name = "value") - public Integer value; + final int value; + + public Checkmark(Habit habit, long timestamp, int value) + { + this.habit = habit; + this.timestamp = timestamp; + this.value = value; + } + + public long getTimestamp() + { + return timestamp; + } + + public int getValue() + { + return value; + } + + @Override + public String toString() + { + return new ToStringBuilder(this) + .append("timestamp", timestamp) + .append("value", value) + .toString(); + } } 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 c0306cdb3..054aa35b3 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -19,18 +19,10 @@ package org.isoron.uhabits.models; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.activeandroid.Cache; -import com.activeandroid.query.Delete; -import com.activeandroid.query.Select; - import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; import java.io.IOException; import java.io.Writer; @@ -38,9 +30,13 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; -public class CheckmarkList +/** + * The collection of {@link Checkmark}s belonging to a habit. + */ +public abstract class CheckmarkList { - private Habit habit; + protected Habit habit; + public ModelObservable observable = new ModelObservable(); public CheckmarkList(Habit habit) @@ -49,125 +45,123 @@ public class CheckmarkList } /** - * Deletes every checkmark that has timestamp either equal or newer than a given timestamp. - * These checkmarks will be recomputed at the next time they are queried. + * Returns the values for all the checkmarks, since the oldest repetition of + * the habit until today. If there are no repetitions at all, returns an + * empty array. + *

+ * The values are returned in an array containing one integer value for each + * day since the first repetition of the habit until today. The first entry + * corresponds to today, the second entry corresponds to yesterday, and so + * on. * - * @param timestamp the timestamp + * @return values for the checkmarks in the interval */ - public void deleteNewerThan(long timestamp) + @NonNull + public int[] getAllValues() { - new Delete().from(Checkmark.class) - .where("habit = ?", habit.getId()) - .and("timestamp >= ?", timestamp) - .execute(); + Repetition oldestRep = habit.getRepetitions().getOldest(); + if (oldestRep == null) return new int[0]; - observable.notifyListeners(); + Long fromTimestamp = oldestRep.getTimestamp(); + Long toTimestamp = DateUtils.getStartOfToday(); + + return getValues(fromTimestamp, toTimestamp); } /** - * Returns the values of the checkmarks that fall inside a certain interval of time. - * - * The values are returned in an array containing one integer value for each day of the - * interval. The first entry corresponds to the most recent day in the interval. Each subsequent - * entry corresponds to one day older than the previous entry. The boundaries of the time - * interval are included. + * Returns the checkmark for today. * - * @param fromTimestamp timestamp for the oldest checkmark - * @param toTimestamp timestamp for the newest checkmark - * @return values for the checkmarks inside the given interval + * @return checkmark for today */ - @NonNull - public int[] getValues(long fromTimestamp, long toTimestamp) + @Nullable + public Checkmark getToday() { - compute(fromTimestamp, toTimestamp); - - if(fromTimestamp > toTimestamp) return new int[0]; - - String query = "select value, timestamp from Checkmarks where " + - "habit = ? and timestamp >= ? and timestamp <= ?"; - - SQLiteDatabase db = Cache.openDatabase(); - String args[] = { habit.getId().toString(), Long.toString(fromTimestamp), - Long.toString(toTimestamp) }; - Cursor cursor = db.rawQuery(query, args); - - long day = DateUtils.millisecondsInOneDay; - int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1; - int[] checks = new int[nDays]; - - if (cursor.moveToFirst()) - { - do - { - long timestamp = cursor.getLong(1); - int offset = (int) ((timestamp - fromTimestamp) / day); - checks[nDays - offset - 1] = cursor.getInt(0); - - } while (cursor.moveToNext()); - } - - cursor.close(); - return checks; + long today = DateUtils.getStartOfToday(); + compute(today, today); + return getNewest(); } /** - * Returns the values for all the checkmarks, since the oldest repetition of the habit until - * today. If there are no repetitions at all, returns an empty array. - * - * The values are returned in an array containing one integer value for each day since the - * first repetition of the habit until today. The first entry corresponds to today, the second - * entry corresponds to yesterday, and so on. + * Returns the value of today's checkmark. * - * @return values for the checkmarks in the interval + * @return value of today's checkmark */ - @NonNull - public int[] getAllValues() + public int getTodayValue() { - Repetition oldestRep = habit.repetitions.getOldest(); - if(oldestRep == null) return new int[0]; + Checkmark today = getToday(); + if (today != null) return today.getValue(); + else return Checkmark.UNCHECKED; + } - Long fromTimestamp = oldestRep.timestamp; - Long toTimestamp = DateUtils.getStartOfToday(); + /** + * Returns the values of the checkmarks that fall inside a certain interval + * of time. + *

+ * The values are returned in an array containing one integer value for each + * day of the interval. The first entry corresponds to the most recent day + * in the interval. Each subsequent entry corresponds to one day older than + * the previous entry. The boundaries of the time interval are included. + * + * @param from timestamp for the oldest checkmark + * @param to timestamp for the newest checkmark + * @return values for the checkmarks inside the given interval + */ + public abstract int[] getValues(long from, long to); - return getValues(fromTimestamp, toTimestamp); - } + /** + * Marks as invalid every checkmark that has timestamp either equal or newer + * than a given timestamp. These checkmarks will be recomputed at the next + * time they are queried. + * + * @param timestamp the timestamp + */ + public abstract void invalidateNewerThan(long timestamp); /** - * Computes and stores one checkmark for each day, since the first repetition until today. - * Days that already have a corresponding checkmark are skipped. + * Writes the entire list of checkmarks to the given writer, in CSV format. + * There is one line for each checkmark. Each line contains two fields: + * timestamp and value. + * + * @param out the writer where the CSV will be output + * @throws IOException in case write operations fail */ - protected void computeAll() + public void writeCSV(Writer out) throws IOException { - long fromTimestamp = habit.repetitions.getOldestTimestamp(); - if(fromTimestamp == 0) return; + computeAll(); - Long toTimestamp = DateUtils.getStartOfToday(); + int values[] = getAllValues(); + long timestamp = DateUtils.getStartOfToday(); + SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); - compute(fromTimestamp, toTimestamp); + for (int value : values) + { + String date = dateFormat.format(new Date(timestamp)); + out.write(String.format("%s,%d\n", date, value)); + timestamp -= DateUtils.millisecondsInOneDay; + } } /** - * Computes and stores one checkmark for each day that falls inside the specified interval of - * time. Days that already have a corresponding checkmark are skipped. + * Computes and stores one checkmark for each day that falls inside the + * specified interval of time. Days that already have a corresponding + * checkmark are skipped. * * @param from timestamp for the beginning of the interval - * @param to timestamp for the end of the interval + * @param to timestamp for the end of the interval */ protected void compute(long from, final long to) { - InterfaceUtils.throwIfMainThread(); - final long day = DateUtils.millisecondsInOneDay; - Checkmark newestCheckmark = findNewest(); - if(newestCheckmark != null) from = newestCheckmark.timestamp + day; + Checkmark newestCheckmark = getNewest(); + if (newestCheckmark != null) + from = newestCheckmark.getTimestamp() + day; - if(from > to) return; + if (from > to) return; - long fromExtended = from - (long) (habit.freqDen) * day; - List reps = habit.repetitions - .selectFromTo(fromExtended, to) - .execute(); + long fromExtended = from - (long) (habit.getFreqDen()) * day; + List reps = + habit.getRepetitions().getByInterval(fromExtended, to); final int nDays = (int) ((to - from) / day) + 1; int nDaysExtended = (int) ((to - fromExtended) / day) + 1; @@ -175,7 +169,7 @@ public class CheckmarkList for (Repetition rep : reps) { - int offset = (int) ((rep.timestamp - fromExtended) / day); + int offset = (int) ((rep.getTimestamp() - fromExtended) / day); checks[nDaysExtended - offset - 1] = Checkmark.CHECKED_EXPLICITLY; } @@ -183,11 +177,11 @@ public class CheckmarkList { int counter = 0; - for (int j = 0; j < habit.freqDen; j++) + for (int j = 0; j < habit.getFreqDen(); j++) if (checks[i + j] == 2) counter++; - if (counter >= habit.freqNum) - if(checks[i] != Checkmark.CHECKED_EXPLICITLY) + if (counter >= habit.getFreqNum()) + if (checks[i] != Checkmark.CHECKED_EXPLICITLY) checks[i] = Checkmark.CHECKED_IMPLICITLY; } @@ -199,106 +193,29 @@ public class CheckmarkList insert(timestamps, checks); } - private void insert(long timestamps[], int values[]) - { - String query = "insert into Checkmarks(habit, timestamp, value) values (?,?,?)"; - - SQLiteDatabase db = Cache.openDatabase(); - db.beginTransaction(); - - try - { - SQLiteStatement statement = db.compileStatement(query); - - for (int i = 0; i < timestamps.length; i++) - { - statement.bindLong(1, habit.getId()); - statement.bindLong(2, timestamps[i]); - statement.bindLong(3, values[i]); - statement.execute(); - } - - db.setTransactionSuccessful(); - } - finally - { - db.endTransaction(); - } - } - /** - * Returns newest checkmark that has already been computed. Ignores any checkmark that has - * timestamp in the future. This does not update the cache. - * - * @return newest checkmark already computed + * Computes and stores one checkmark for each day, since the first + * repetition until today. Days that already have a corresponding checkmark + * are skipped. */ - @Nullable - protected Checkmark findNewest() + protected void computeAll() { - return new Select().from(Checkmark.class) - .where("habit = ?", habit.getId()) - .and("timestamp <= ?", DateUtils.getStartOfToday()) - .orderBy("timestamp desc") - .limit(1) - .executeSingle(); - } + Repetition oldest = habit.getRepetitions().getOldest(); + if (oldest == null) return; - /** - * Returns the checkmark for today. - * - * @return checkmark for today - */ - @Nullable - public Checkmark getToday() - { - long today = DateUtils.getStartOfToday(); - compute(today, today); - return findNewest(); - } + Long today = DateUtils.getStartOfToday(); - /** - * Returns the value of today's checkmark. - * - * @return value of today's checkmark - */ - public int getTodayValue() - { - Checkmark today = getToday(); - if(today != null) return today.value; - else return Checkmark.UNCHECKED; + compute(oldest.getTimestamp(), today); } /** - * Writes the entire list of checkmarks to the given writer, in CSV format. There is one - * line for each checkmark. Each line contains two fields: timestamp and value. + * Returns newest checkmark that has already been computed. Ignores any + * checkmark that has timestamp in the future. This does not update the + * cache. * - * @param out the writer where the CSV will be output - * @throws IOException in case write operations fail + * @return newest checkmark already computed */ + protected abstract Checkmark getNewest(); - public void writeCSV(Writer out) throws IOException - { - computeAll(); - - SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); - - String query = "select timestamp, value from checkmarks where habit = ? order by timestamp"; - String params[] = { habit.getId().toString() }; - - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); - - if(!cursor.moveToFirst()) return; - - do - { - String timestamp = dateFormat.format(new Date(cursor.getLong(0))); - Integer value = cursor.getInt(1); - out.write(String.format("%s,%d\n", timestamp, value)); - - } while(cursor.moveToNext()); - - cursor.close(); - out.close(); - } + protected abstract void insert(long timestamps[], int values[]); } diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 3d4066823..aeaf152b1 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -19,135 +19,75 @@ package org.isoron.uhabits.models; -import android.annotation.SuppressLint; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.activeandroid.ActiveAndroid; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; -import com.activeandroid.query.Delete; -import com.activeandroid.query.From; -import com.activeandroid.query.Select; -import com.activeandroid.query.Update; -import com.activeandroid.util.SQLiteUtils; -import com.opencsv.CSVWriter; - -import org.isoron.uhabits.utils.ColorUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.utils.DateUtils; -import java.io.IOException; -import java.io.Writer; -import java.util.List; import java.util.Locale; -@Table(name = "Habits") -public class Habit extends Model +import javax.inject.Inject; + +/** + * The thing that the user wants to track. + */ +public class Habit { - /** - * Name of the habit - */ - @Column(name = "name") - public String name; + public static final String HABIT_URI_FORMAT = + "content://org.isoron.uhabits/habit/%d"; - /** - * Description of the habit - */ - @Column(name = "description") - public String description; + @Nullable + private Long id; - /** - * Frequency numerator. If a habit is performed 3 times in 7 days, this field equals 3. - */ - @Column(name = "freq_num") - public Integer freqNum; + @NonNull + private String name; - /** - * Frequency denominator. If a habit is performed 3 times in 7 days, this field equals 7. - */ - @Column(name = "freq_den") - public Integer freqDen; + @NonNull + private String description; - /** - * Color of the habit. - * - * This number is not an android.graphics.Color, but an index to the activity color palette, - * which changes according to the theme. To convert this color into an android.graphics.Color, - * use ColorHelper.getColor(context, habit.color). - */ - @Column(name = "color") - public Integer color; + @NonNull + private Integer freqNum; - /** - * Position of the habit. Habits are usually sorted by this field. - */ - @Column(name = "position") - public Integer position; + @NonNull + private Integer freqDen; + + @NonNull + private Integer color; - /** - * Hour of the day the reminder should be shown. If there is no reminder, this equals to null. - */ @Nullable - @Column(name = "reminder_hour") - public Integer reminderHour; + private Integer reminderHour; - /** - * Minute the reminder should be shown. If there is no reminder, this equals to null. - */ @Nullable - @Column(name = "reminder_min") - public Integer reminderMin; + private Integer reminderMin; - /** - * Days of the week the reminder should be shown. This field can be converted to a list of - * booleans using the method DateHelper.unpackWeekdayList and converted back to an integer by - * using the method DateHelper.packWeekdayList. If the habit has no reminders, this value - * should be ignored. - */ @NonNull - @Column(name = "reminder_days") - public Integer reminderDays; + private Integer reminderDays; - /** - * Not currently used. - */ - @Column(name = "highlight") - public Integer highlight; + @NonNull + private Integer highlight; - /** - * Flag that indicates whether the habit is archived. Archived habits are usually omitted from - * listings, unless explicitly included. - */ - @Column(name = "archived") - public Integer archived; + @NonNull + private Integer archived; - /** - * List of streaks belonging to this habit. - */ @NonNull - public StreakList streaks; + private StreakList streaks; - /** - * List of scores belonging to this habit. - */ @NonNull - public ScoreList scores; + private ScoreList scores; - /** - * List of repetitions belonging to this habit. - */ @NonNull - public RepetitionList repetitions; + private RepetitionList repetitions; - /** - * List of checkmarks belonging to this habit. - */ @NonNull - public CheckmarkList checkmarks; + private CheckmarkList checkmarks; + + private ModelObservable observable = new ModelObservable(); - public ModelObservable observable = new ModelObservable(); + @Inject + ModelFactory factory; /** * Constructs a habit with the same attributes as the specified habit. @@ -156,320 +96,281 @@ public class Habit extends Model */ public Habit(Habit model) { + HabitsApplication.getComponent().inject(this); + reminderDays = DateUtils.ALL_WEEK_DAYS; - copyAttributes(model); + copyFrom(model); - checkmarks = new CheckmarkList(this); - streaks = new StreakList(this); - scores = new ScoreList(this); - repetitions = new RepetitionList(this); + checkmarks = factory.buildCheckmarkList(this); + streaks = factory.buildStreakList(this); + scores = factory.buildScoreList(this); + repetitions = factory.buidRepetitionList(this); } /** - * Constructs a habit with default attributes. The habit is not archived, not highlighted, has - * no reminders and is placed in the last position of the list of habits. + * Constructs a habit with default attributes. + *

+ * The habit is not archived, not highlighted, has no reminders and is + * placed in the last position of the list of habits. */ public Habit() { + HabitsApplication.getComponent().inject(this); + this.color = 5; - this.position = Habit.countWithArchived(); this.highlight = 0; this.archived = 0; this.freqDen = 7; this.freqNum = 3; this.reminderDays = DateUtils.ALL_WEEK_DAYS; - checkmarks = new CheckmarkList(this); - streaks = new StreakList(this); - scores = new ScoreList(this); - repetitions = new RepetitionList(this); + checkmarks = factory.buildCheckmarkList(this); + streaks = factory.buildStreakList(this); + scores = factory.buildScoreList(this); + repetitions = factory.buidRepetitionList(this); } /** - * Returns the habit with specified id. - * - * @param id the id of the habit - * @return the habit, or null if none exist + * Clears the reminder for a habit. This sets all the related fields to + * null. */ - @Nullable - public static Habit get(long id) + public void clearReminder() { - return Habit.load(Habit.class, id); + reminderHour = null; + reminderMin = null; + reminderDays = DateUtils.ALL_WEEK_DAYS; + observable.notifyListeners(); } /** - * Returns a list of all habits, optionally including archived habits. + * Copies all the attributes of the specified habit into this habit * - * @param includeArchive whether archived habits should be included the list - * @return list of all habits + * @param model the model whose attributes should be copied from */ - @NonNull - public static List getAll(boolean includeArchive) + public void copyFrom(@NonNull Habit model) { - if(includeArchive) return selectWithArchived().execute(); - else return select().execute(); + this.name = model.getName(); + this.description = model.getDescription(); + this.freqNum = model.getFreqNum(); + this.freqDen = model.getFreqDen(); + this.color = model.getColor(); + this.reminderHour = model.getReminderHour(); + this.reminderMin = model.getReminderMin(); + this.reminderDays = model.getReminderDays(); + this.highlight = model.getHighlight(); + this.archived = model.getArchived(); + observable.notifyListeners(); } /** - * Returns the habit that occupies a certain position. - * - * @param position the position of the desired habit - * @return the habit at that position, or null if there is none + * Flag that indicates whether the habit is archived. Archived habits are + * usually omitted from listings, unless explicitly included. */ - @Nullable - public static Habit getByPosition(int position) + public Integer getArchived() { - return selectWithArchived().where("position = ?", position).executeSingle(); + return archived; } /** - * Changes the id of a habit on the database. - * - * @param oldId the original id - * @param newId the new id + * List of checkmarks belonging to this habit. */ - @SuppressLint("DefaultLocale") - public static void updateId(long oldId, long newId) + @NonNull + public CheckmarkList getCheckmarks() { - SQLiteUtils.execSql(String.format("update Habits set Id = %d where Id = %d", newId, oldId)); + return checkmarks; } - @NonNull - protected static From select() + /** + * Color of the habit. + *

+ * This number is not an android.graphics.Color, but an index to the + * activity color palette, which changes according to the theme. To convert + * this color into an android.graphics.Color, use ColorHelper.getColor(context, + * habit.color). + */ + public Integer getColor() { - return new Select().from(Habit.class).where("archived = 0").orderBy("position"); + return color; } - @NonNull - protected static From selectWithArchived() + public void setColor(Integer color) { - return new Select().from(Habit.class).orderBy("position"); + this.color = color; } /** - * Returns the total number of unarchived habits. - * - * @return number of unarchived habits + * Description of the habit */ - public static int count() + public String getDescription() { - return select().count(); + return description; } - /** - * Returns the total number of habits, including archived habits. - * - * @return number of habits, including archived - */ - public static int countWithArchived() + public void setDescription(String description) { - return selectWithArchived().count(); + this.description = description; } /** - * Returns a list the habits that have a reminder. Does not include archived habits. - * - * @return list of habits with reminder + * Frequency denominator. If a habit is performed 3 times in 7 days, this + * field equals 7. */ - @NonNull - public static List getHabitsWithReminder() + public Integer getFreqDen() + { + return freqDen; + } + + public void setFreqDen(Integer freqDen) { - return select().where("reminder_hour is not null").execute(); + this.freqDen = freqDen; } /** - * Changes the position of a habit on the list. - * - * @param from the habit that should be moved - * @param to the habit that currently occupies the desired position + * Frequency numerator. If a habit is performed 3 times in 7 days, this + * field equals 3. */ - public static void reorder(Habit from, Habit to) + public Integer getFreqNum() { - if(from == to) return; - - if (to.position < from.position) - { - new Update(Habit.class).set("position = position + 1") - .where("position >= ? and position < ?", to.position, from.position) - .execute(); - } - else - { - new Update(Habit.class).set("position = position - 1") - .where("position > ? and position <= ?", from.position, to.position) - .execute(); - } + return freqNum; + } - from.position = to.position; - from.save(); + public void setFreqNum(Integer freqNum) + { + this.freqNum = freqNum; } /** - * Recomputes the position for every habit in the database. It should never be necessary - * to call this method. + * Not currently used. */ - public static void rebuildOrder() + public Integer getHighlight() { - List habits = selectWithArchived().execute(); + return highlight; + } - ActiveAndroid.beginTransaction(); - try - { - int i = 0; - for (Habit h : habits) - { - h.position = i++; - h.save(); - } + public void setHighlight(Integer highlight) + { + this.highlight = highlight; + } - ActiveAndroid.setTransactionSuccessful(); - } - finally - { - ActiveAndroid.endTransaction(); - } + public Long getId() + { + return id; + } + public void setId(Long id) + { + this.id = id; } /** - * Copies all the attributes of the specified habit into this habit - * - * @param model the model whose attributes should be copied from + * Name of the habit */ - public void copyAttributes(@NonNull Habit model) - { - this.name = model.name; - this.description = model.description; - this.freqNum = model.freqNum; - this.freqDen = model.freqDen; - this.color = model.color; - this.position = model.position; - this.reminderHour = model.reminderHour; - this.reminderMin = model.reminderMin; - this.reminderDays = model.reminderDays; - this.highlight = model.highlight; - this.archived = model.archived; + public String getName() + { + return name; + } - observable.notifyListeners(); + public void setName(String name) + { + this.name = name; } - /** - * Saves the habit on the database, and assigns the specified id to it. - * - * @param id the id that the habit should receive - */ - public void save(long id) + public ModelObservable getObservable() { - save(); - Habit.updateId(getId(), id); + return observable; } /** - * Deletes the habit and all data associated to it, including checkmarks, repetitions and - * scores. + * Days of the week the reminder should be shown. This field can be + * converted to a list of booleans using the method DateHelper.unpackWeekdayList + * and converted back to an integer by using the method + * DateHelper.packWeekdayList. If the habit has no reminders, this value + * should be ignored. */ - public void cascadeDelete() + @NonNull + public Integer getReminderDays() { - Long id = getId(); - - ActiveAndroid.beginTransaction(); - try - { - new Delete().from(Checkmark.class).where("habit = ?", id).execute(); - new Delete().from(Repetition.class).where("habit = ?", id).execute(); - new Delete().from(Score.class).where("habit = ?", id).execute(); - new Delete().from(Streak.class).where("habit = ?", id).execute(); - delete(); + return reminderDays; + } - ActiveAndroid.setTransactionSuccessful(); - } - finally - { - ActiveAndroid.endTransaction(); - } + public void setReminderDays(@NonNull Integer reminderDays) + { + this.reminderDays = reminderDays; } /** - * Returns the public URI that identifies this habit - * @return the uri + * Hour of the day the reminder should be shown. If there is no reminder, + * this equals to null. */ - public Uri getUri() + @Nullable + public Integer getReminderHour() { - String s = String.format(Locale.US, "content://org.isoron.uhabits/habit/%d", getId()); - return Uri.parse(s); + return reminderHour; + } + + public void setReminderHour(@Nullable Integer reminderHour) + { + this.reminderHour = reminderHour; } /** - * Returns whether the habit is archived or not. - * @return true if archived + * Minute the reminder should be shown. If there is no reminder, this equals + * to null. */ - public boolean isArchived() + @Nullable + public Integer getReminderMin() { - return archived != 0; + return reminderMin; } - private static void updateAttributes(@NonNull List habits, @Nullable Integer color, - @Nullable Integer archived) + public void setReminderMin(@Nullable Integer reminderMin) { - ActiveAndroid.beginTransaction(); - - try - { - for (Habit h : habits) - { - if(color != null) h.color = color; - if(archived != null) h.archived = archived; - h.save(); - } + this.reminderMin = reminderMin; + } - ActiveAndroid.setTransactionSuccessful(); - } - finally - { - ActiveAndroid.endTransaction(); - for(Habit h : habits) - h.observable.notifyListeners(); - } + /** + * List of repetitions belonging to this habit. + */ + @NonNull + public RepetitionList getRepetitions() + { + return repetitions; } /** - * Archives an entire list of habits - * - * @param habits the habits to be archived + * List of scores belonging to this habit. */ - public static void archive(@NonNull List habits) + @NonNull + public ScoreList getScores() { - updateAttributes(habits, null, 1); + return scores; } /** - * Unarchives an entire list of habits - * - * @param habits the habits to be unarchived + * List of streaks belonging to this habit. */ - public static void unarchive(@NonNull List habits) + @NonNull + public StreakList getStreaks() { - updateAttributes(habits, null, 0); + return streaks; } /** - * Sets the color for an entire list of habits. + * Returns the public URI that identifies this habit * - * @param habits the habits to be modified - * @param color the new color to be set + * @return the uri */ - public static void setColor(@NonNull List habits, int color) + public Uri getUri() { - updateAttributes(habits, color, null); - for(Habit h : habits) - h.observable.notifyListeners(); + String s = String.format(Locale.US, HABIT_URI_FORMAT, getId()); + return Uri.parse(s); } /** * Checks whether the habit has a reminder set. * - * @return true if habit has reminder + * @return true if habit has reminder, false otherwise */ public boolean hasReminder() { @@ -477,47 +378,35 @@ public class Habit extends Model } /** - * Clears the reminder for a habit. This sets all the related fields to null. + * Returns whether the habit is archived or not. + * + * @return true if archived */ - public void clearReminder() + public boolean isArchived() { - reminderHour = null; - reminderMin = null; - reminderDays = DateUtils.ALL_WEEK_DAYS; - observable.notifyListeners(); + return archived != 0; } - /** - * Writes the list of habits to the given writer, in CSV format. There is one line for each - * habit, containing the fields name, description, frequency numerator, frequency denominator - * and color. The color is written in HTML format (#000000). - * - * @param habits the list of habits to write - * @param out the writer that will receive the result - * @throws IOException if write operations fail - */ - public static void writeCSV(List habits, Writer out) throws IOException + public void setArchived(Integer archived) { - String header[] = { "Position", "Name", "Description", "NumRepetitions", "Interval", "Color" }; - - CSVWriter csv = new CSVWriter(out); - csv.writeNext(header, false); - - for(Habit habit : habits) - { - String[] cols = - { - String.format("%03d", habit.position + 1), - habit.name, - habit.description, - Integer.toString(habit.freqNum), - Integer.toString(habit.freqDen), - ColorUtils.toHTML(ColorUtils.CSV_PALETTE[habit.color]) - }; - - csv.writeNext(cols, false); - } + this.archived = archived; + } - csv.close(); + @Override + public String toString() + { + return new ToStringBuilder(this) + .append("id", id) + .append("name", name) + .append("description", description) + .append("freqNum", freqNum) + .append("freqDen", freqDen) + .append("color", color) + .append("reminderHour", reminderHour) + .append("reminderMin", reminderMin) + .append("reminderDays", reminderDays) + .append("highlight", highlight) + .append("archived", archived) + .toString(); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/HabitList.java b/app/src/main/java/org/isoron/uhabits/models/HabitList.java new file mode 100644 index 000000000..8c8fd8226 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/HabitList.java @@ -0,0 +1,235 @@ +/* + * 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; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.opencsv.CSVWriter; + +import org.isoron.uhabits.utils.ColorUtils; + +import java.io.IOException; +import java.io.Writer; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * An ordered collection of {@link Habit}s. + */ +public abstract class HabitList +{ + private ModelObservable observable; + + /** + * Creates a new HabitList. + *

+ * Depending on the implementation, this list can either be empty or be + * populated by some pre-existing habits. + */ + public HabitList() + { + observable = new ModelObservable(); + } + + /** + * Inserts a new habit in the list. + * + * @param habit the habit to be inserted + */ + public abstract void add(Habit habit); + + /** + * Returns the total number of unarchived habits. + * + * @return number of unarchived habits + */ + public abstract int count(); + + /** + * Returns the total number of habits, including archived habits. + * + * @return number of habits, including archived + */ + public abstract int countWithArchived(); + + /** + * Returns a list of all habits, optionally including archived habits. + * + * @param includeArchive whether archived habits should be included the + * list + * @return list of all habits + */ + @NonNull + public abstract List getAll(boolean includeArchive); + + /** + * Returns the habit with specified id. + * + * @param id the id of the habit + * @return the habit, or null if none exist + */ + public abstract Habit getById(long id); + + /** + * Returns the habit that occupies a certain position. + * + * @param position the position of the desired habit + * @return the habit at that position, or null if there is none + */ + @Nullable + public abstract Habit getByPosition(int position); + + /** + * Returns the list of habits that match a given condition. + * + * @param matcher the matcher that checks the condition + * @return the list of matching habits + */ + @NonNull + public List getFiltered(HabitMatcher matcher) + { + LinkedList habits = new LinkedList<>(); + for (Habit h : getAll(true)) if (matcher.matches(h)) habits.add(h); + return habits; + } + + public ModelObservable getObservable() + { + return observable; + } + + /** + * Returns a list the habits that have a reminder. Does not include archived + * habits. + * + * @return list of habits with reminder + */ + @NonNull + public List getWithReminder() + { + return getFiltered(habit -> habit.hasReminder()); + } + + /** + * Returns the index of the given habit in the list, or -1 if the list does + * not contain the habit. + * + * @param h the habit + * @return the index of the habit, or -1 if not in the list + */ + public abstract int indexOf(Habit h); + + /** + * Removes the given habit from the list. + *

+ * If the given habit is not in the list, does nothing. + * + * @param h the habit to be removed. + */ + public abstract void remove(@NonNull Habit h); + + /** + * Changes the position of a habit in the list. + * + * @param from the habit that should be moved + * @param to the habit that currently occupies the desired position + */ + public abstract void reorder(Habit from, Habit to); + + /** + * Notifies the list that a certain list of habits has been modified. + *

+ * Depending on the implementation, this operation might trigger a write to + * disk, or do nothing at all. To make sure that the habits get persisted, + * this operation must be called. + * + * @param habits the list of habits that have been modified. + */ + public abstract void update(List habits); + + /** + * Notifies the list that a certain habit has been modified. + *

+ * See {@link #update(List)} for more details. + * + * @param habit the habit that has been modified. + */ + public void update(Habit habit) + { + update(Collections.singletonList(habit)); + } + + /** + * Writes the list of habits to the given writer, in CSV format. There is + * one line for each habit, containing the fields name, description, + * frequency numerator, frequency denominator and color. The color is + * written in HTML format (#000000). + * + * @param out the writer that will receive the result + * @throws IOException if write operations fail + */ + public void writeCSV(Writer out) throws IOException + { + String header[] = { + "Position", + "Name", + "Description", + "NumRepetitions", + "Interval", + "Color" + }; + + CSVWriter csv = new CSVWriter(out); + csv.writeNext(header, false); + + for (Habit habit : getAll(true)) + { + String[] cols = { + String.format("%03d", indexOf(habit) + 1), + habit.getName(), + habit.getDescription(), + Integer.toString(habit.getFreqNum()), + Integer.toString(habit.getFreqDen()), + ColorUtils.CSV_PALETTE[habit.getColor()] + }; + + csv.writeNext(cols, false); + } + + csv.close(); + } + + /** + * A HabitMatcher decides whether habits match or not a certain condition. + * They can be used to produce filtered lists of habits. + */ + public interface HabitMatcher + { + /** + * Returns true if the given habit matches. + * + * @param habit the habit to be checked. + * @return true if matches, false otherwise. + */ + boolean matches(Habit habit); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/ModelFactory.java b/app/src/main/java/org/isoron/uhabits/models/ModelFactory.java new file mode 100644 index 000000000..c1300ac71 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/ModelFactory.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * Interface implemented by factories that provide concrete implementations + * of the core model classes. + */ +public interface ModelFactory +{ + RepetitionList buidRepetitionList(Habit habit); + + HabitList buildHabitList(); + + CheckmarkList buildCheckmarkList(Habit habit); + + ScoreList buildScoreList(Habit habit); + + StreakList buildStreakList(Habit habit); +} diff --git a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java index 2ff7650f3..ffd8a8499 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java +++ b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java @@ -22,33 +22,62 @@ package org.isoron.uhabits.models; import java.util.LinkedList; import java.util.List; +/** + * A ModelObservable allows objects to subscribe themselves to it and receive + * notifications whenever the model is changed. + */ public class ModelObservable { List listeners; + /** + * Creates a new ModelObservable with no listeners. + */ public ModelObservable() { super(); listeners = new LinkedList<>(); } - public interface Listener + /** + * Adds the given listener to the observable. + * + * @param l the listener to be added. + */ + public void addListener(Listener l) { - void onModelChange(); + listeners.add(l); } - public void addListener(Listener l) + /** + * Notifies every listener that the model has changed. + *

+ * Only models should call this method. + */ + public void notifyListeners() { - listeners.add(l); + for (Listener l : listeners) l.onModelChange(); } + /** + * Removes the given listener. + *

+ * The listener will no longer be notified when the model changes. If the + * given listener is not subscrined to this observable, does nothing. + * + * @param l the listener to be removed + */ public void removeListener(Listener l) { listeners.remove(l); } - public void notifyListeners() + /** + * Interface implemented by objects that want to be notified when the model + * changes. + */ + public interface Listener { - for(Listener l : listeners) l.onModelChange(); + void onModelChange(); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/Repetition.java b/app/src/main/java/org/isoron/uhabits/models/Repetition.java index f43243698..b3e8b2771 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Repetition.java +++ b/app/src/main/java/org/isoron/uhabits/models/Repetition.java @@ -19,22 +19,51 @@ package org.isoron.uhabits.models; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; +import android.support.annotation.NonNull; -@Table(name = "Repetitions") -public class Repetition extends Model +import org.apache.commons.lang3.builder.ToStringBuilder; + +/** + * Represents a record that the user has performed a certain habit at a certain + * date. + */ +public class Repetition { - /** - * Habit to which this repetition belong. - */ - @Column(name = "habit") - public Habit habit; + @NonNull + private final Habit habit; + + private final long timestamp; /** - * Timestamp of the day this repetition occurred. Time of day should be midnight (UTC). + * Creates a new repetition with given parameters. + *

+ * The timestamp corresponds to the days this repetition occurred. Time of + * day must be midnight (UTC). + * + * @param habit the habit to which this repetition belongs. + * @param timestamp the time this repetition occurred. */ - @Column(name = "timestamp") - public Long timestamp; + public Repetition(Habit habit, long timestamp) + { + this.habit = habit; + this.timestamp = timestamp; + } + + public Habit getHabit() + { + return habit; + } + + public long getTimestamp() + { + return timestamp; + } + + @Override + public String toString() + { + return new ToStringBuilder(this) + .append("timestamp", timestamp) + .toString(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index f9766645d..051825805 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -19,199 +19,179 @@ package org.isoron.uhabits.models; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.activeandroid.Cache; -import com.activeandroid.query.Delete; -import com.activeandroid.query.From; -import com.activeandroid.query.Select; -import com.activeandroid.util.SQLiteUtils; - -import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.utils.DateUtils; import java.util.Arrays; -import java.util.GregorianCalendar; +import java.util.Calendar; import java.util.HashMap; +import java.util.List; -public class RepetitionList +/** + * The collection of {@link Repetition}s belonging to a habit. + */ +public abstract class RepetitionList { @NonNull - private Habit habit; - public ModelObservable observable = new ModelObservable(); - - public RepetitionList(@NonNull Habit habit) - { - this.habit = habit; - } + protected final Habit habit; @NonNull - protected From select() - { - return new Select().from(Repetition.class) - .where("habit = ?", habit.getId()) - .and("timestamp <= ?", DateUtils.getStartOfToday()) - .orderBy("timestamp"); - } + protected final ModelObservable observable; - @NonNull - protected From selectFromTo(long timeFrom, long timeTo) + public RepetitionList(@NonNull Habit habit) { - return select().and("timestamp >= ?", timeFrom).and("timestamp <= ?", timeTo); + this.habit = habit; + this.observable = new ModelObservable(); } /** - * Checks whether there is a repetition at a given timestamp. + * Adds a repetition to the list. + *

+ * Any implementation of this method must call observable.notifyListeners() + * after the repetition has been added. * - * @param timestamp the timestamp to check - * @return true if there is a repetition + * @param repetition the repetition to be added. */ - public boolean contains(long timestamp) - { - int count = select().where("timestamp = ?", timestamp).count(); - return (count > 0); - } + public abstract void add(Repetition repetition); /** - * Deletes the repetition at a given timestamp, if it exists. + * Returns true if the list contains a repetition that has the given + * timestamp. * - * @param timestamp the timestamp of the repetition to delete + * @param timestamp the timestamp to find. + * @return true if list contains repetition with given timestamp, false + * otherwise. */ - public void delete(long timestamp) + public boolean containsTimestamp(long timestamp) { - new Delete().from(Repetition.class) - .where("habit = ?", habit.getId()) - .and("timestamp = ?", timestamp) - .execute(); + return (getByTimestamp(timestamp) != null); } /** - * Toggles the repetition at a certain timestamp. That is, deletes the repetition if it exists - * or creates one if it does not. + * Returns the list of repetitions that happened within the given time + * interval. + * + * The list is sorted by timestamp in decreasing order. That is, the first + * element corresponds to the most recent timestamp. The endpoints of the + * interval are included. * - * @param timestamp the timestamp of the repetition to toggle + * @param fromTimestamp timestamp of the beginning of the interval + * @param toTimestamp timestamp of the end of the interval + * @return list of repetitions within given time interval */ - public void toggle(long timestamp) - { - timestamp = DateUtils.getStartOfDay(timestamp); - - if (contains(timestamp)) - delete(timestamp); - else - insert(timestamp); - - habit.scores.invalidateNewerThan(timestamp); - habit.checkmarks.deleteNewerThan(timestamp); - habit.streaks.deleteNewerThan(timestamp); - observable.notifyListeners(); - } - - private void insert(long timestamp) - { - String[] args = { habit.getId().toString(), Long.toString(timestamp) }; - SQLiteUtils.execSql("insert into Repetitions(habit, timestamp) values (?,?)", args); - } + public abstract List getByInterval(long fromTimestamp, + long toTimestamp); /** - * Returns the oldest repetition for the habit. If there is no repetition, returns null. - * Repetitions in the future are discarded. + * Returns the repetition that has the given timestamp, or null if none + * exists. * - * @return oldest repetition for the habit + * @param timestamp the repetition timestamp. + * @return the repetition that has the given timestamp. */ @Nullable - public Repetition getOldest() + public abstract Repetition getByTimestamp(long timestamp); + + @NonNull + public ModelObservable getObservable() { - return (Repetition) select().limit(1).executeSingle(); + return observable; } /** - * Returns the timestamp of the oldest repetition. If there are no repetitions, returns zero. - * Repetitions in the future are discarded. + * Returns the oldest repetition in the list. + *

+ * If the list is empty, returns null. Repetitions in the future are + * discarded. * - * @return timestamp of the oldest repetition + * @return oldest repetition in the list, or null if list is empty. */ - public long getOldestTimestamp() - { - String[] args = { habit.getId().toString(), Long.toString(DateUtils.getStartOfToday()) }; - String query = "select timestamp from Repetitions where habit = ? and timestamp <= ? " + - "order by timestamp limit 1"; - - return DatabaseUtils.longQuery(query, args); - } + @Nullable + public abstract Repetition getOldest(); /** - * Returns the total number of repetitions for each month, from the first repetition until - * today, grouped by day of week. The repetitions are returned in a HashMap. The key is the - * timestamp for the first day of the month, at midnight (00:00). The value is an integer - * array with 7 entries. The first entry contains the total number of repetitions during - * the specified month that occurred on a Saturday. The second entry corresponds to Sunday, - * and so on. If there are no repetitions during a certain month, the value is null. + * Returns the total number of repetitions for each month, from the first + * repetition until today, grouped by day of week. + *

+ * The repetitions are returned in a HashMap. The key is the timestamp for + * the first day of the month, at midnight (00:00). The value is an integer + * array with 7 entries. The first entry contains the total number of + * repetitions during the specified month that occurred on a Saturday. The + * second entry corresponds to Sunday, and so on. If there are no + * repetitions during a certain month, the value is null. * * @return total number of repetitions by month versus day of week */ @NonNull public HashMap getWeekdayFrequency() { - Repetition oldestRep = getOldest(); - if(oldestRep == null) return new HashMap<>(); + List reps = getByInterval(0, DateUtils.getStartOfToday()); + HashMap map = new HashMap<>(); - String query = "select strftime('%Y', timestamp / 1000, 'unixepoch') as year," + - "strftime('%m', timestamp / 1000, 'unixepoch') as month," + - "strftime('%w', timestamp / 1000, 'unixepoch') as weekday, " + - "count(*) from repetitions " + - "where habit = ? and timestamp <= ? " + - "group by year, month, weekday"; - - String[] params = { habit.getId().toString(), - Long.toString(DateUtils.getStartOfToday()) }; - - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); - - if(!cursor.moveToFirst()) return new HashMap<>(); - - HashMap map = new HashMap<>(); - GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); - - do + for (Repetition r : reps) { - int year = Integer.parseInt(cursor.getString(0)); - int month = Integer.parseInt(cursor.getString(1)); - int weekday = (Integer.parseInt(cursor.getString(2)) + 1) % 7; - int count = cursor.getInt(3); + Calendar date = DateUtils.getCalendar(r.getTimestamp()); + int weekday = date.get(Calendar.DAY_OF_WEEK) % 7; + date.set(Calendar.DAY_OF_MONTH, 1); - date.set(year, month - 1, 1); long timestamp = date.getTimeInMillis(); - Integer[] list = map.get(timestamp); - if(list == null) + if (list == null) { list = new Integer[7]; Arrays.fill(list, 0); map.put(timestamp, list); } - list[weekday] = count; + list[weekday]++; } - while (cursor.moveToNext()); - cursor.close(); return map; } /** - * Returns the total number of repetitions that happened within the specified interval of time. + * Removes a given repetition from the list. + *

+ * If the list does not contain the repetition, it is unchanged. + *

+ * Any implementation of this method must call observable.notifyListeners() + * after the repetition has been added. * - * @param from beginning of the interval - * @param to end of the interval - * @return number of repetition in the given interval + * @param repetition the repetition to be removed */ - public int count(long from, long to) + public abstract void remove(@NonNull Repetition repetition); + + /** + * Adds or remove a repetition at a certain timestamp. + *

+ * If there exists a repetition on the list with the given timestamp, the + * method removes this repetition from the list and returns it. If there are + * no repetitions with the given timestamp, creates and adds one to the + * list, then returns it. + * + * @param timestamp the timestamp for the timestamp that should be added or + * removed. + * @return the repetition that has been added or removed. + */ + @NonNull + public Repetition toggleTimestamp(long timestamp) { - return selectFromTo(from, to).count(); + timestamp = DateUtils.getStartOfDay(timestamp); + Repetition rep = getByTimestamp(timestamp); + + if (rep != null) remove(rep); + else + { + rep = new Repetition(habit, timestamp); + add(rep); + } + +// habit.getScores().invalidateNewerThan(timestamp); +// habit.getCheckmarks().invalidateNewerThan(timestamp); +// habit.getStreaks().invalidateNewerThan(timestamp); + return rep; } } diff --git a/app/src/main/java/org/isoron/uhabits/models/Score.java b/app/src/main/java/org/isoron/uhabits/models/Score.java index 5eba480e9..5943950fe 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Score.java +++ b/app/src/main/java/org/isoron/uhabits/models/Score.java @@ -19,78 +19,60 @@ package org.isoron.uhabits.models; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; +import org.apache.commons.lang3.builder.ToStringBuilder; -@Table(name = "Score") -public class Score extends Model +/** + * Represents how strong a habit is at a certain date. + */ +public class Score { /** - * Minimum score value required to earn half a star. - */ - public static final int HALF_STAR_CUTOFF = 9629750; - - /** - * Minimum score value required to earn a full star. - */ - public static final int FULL_STAR_CUTOFF = 15407600; - - /** - * Maximum score value attainable by any habit. - */ - public static final int MAX_VALUE = 19259478; - - /** - * Status indicating that the habit has not earned any star. - */ - public static final int EMPTY_STAR = 0; - - /** - * Status indicating that the habit has earned half a star. + * Habit to which this score belongs to. */ - public static final int HALF_STAR = 1; + private Habit habit; /** - * Status indicating that the habit has earned a full star. + * Timestamp of the day to which this score applies. Time of day should be + * midnight (UTC). */ - public static final int FULL_STAR = 2; + private Long timestamp; /** - * Habit to which this score belongs to. + * Value of the score. */ - @Column(name = "habit") - public Habit habit; + private Integer value; /** - * Timestamp of the day to which this score applies. Time of day should be midnight (UTC). + * Maximum score value attainable by any habit. */ - @Column(name = "timestamp") - public Long timestamp; + public static final int MAX_VALUE = 19259478; - /** - * Value of the score. - */ - @Column(name = "score") - public Integer score; + public Score(Habit habit, Long timestamp, Integer value) + { + this.habit = habit; + this.timestamp = timestamp; + this.value = value; + } /** - * Given the frequency of the habit, the previous score, and the value of the current checkmark, - * computes the current score for the habit. + * Given the frequency of the habit, the previous score, and the value of + * the current checkmark, computes the current score for the habit. + *

+ * The frequency of the habit is the number of repetitions divided by the + * length of the interval. For example, a habit that should be repeated 3 + * times in 8 days has frequency 3.0 / 8.0 = 0.375. + *

+ * The checkmarkValue should be UNCHECKED, CHECKED_IMPLICITLY or + * CHECK_EXPLICITLY. * - * The frequency of the habit is the number of repetitions divided by the length of the - * interval. For example, a habit that should be repeated 3 times in 8 days has frequency 3.0 / - * 8.0 = 0.375. - * - * The checkmarkValue should be UNCHECKED, CHECKED_IMPLICITLY or CHECK_EXPLICITLY. - * - * @param frequency the frequency of the habit - * @param previousScore the previous score of the habit + * @param frequency the frequency of the habit + * @param previousScore the previous score of the habit * @param checkmarkValue the value of the current checkmark - * * @return the current score */ - public static int compute(double frequency, int previousScore, int checkmarkValue) + public static int compute(double frequency, + int previousScore, + int checkmarkValue) { double multiplier = Math.pow(0.5, 1.0 / (14.0 / frequency - 1)); int score = (int) (previousScore * multiplier); @@ -104,16 +86,27 @@ public class Score extends Model return score; } - /** - * Return the current star status for the habit, which can one of EMPTY_STAR, HALF_STAR or - * FULL_STAR. - * - * @return current star status - */ - public int getStarStatus() + public Habit getHabit() + { + return habit; + } + + public Long getTimestamp() + { + return timestamp; + } + + public Integer getValue() + { + return value; + } + + @Override + public String toString() { - if(score >= Score.FULL_STAR_CUTOFF) return Score.FULL_STAR; - if(score >= Score.HALF_STAR_CUTOFF) return Score.HALF_STAR; - return Score.EMPTY_STAR; + return new ToStringBuilder(this) + .append("timestamp", timestamp) + .append("value", value) + .toString(); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index e49ec56ca..7b0749a54 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -21,328 +21,222 @@ package org.isoron.uhabits.models; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.activeandroid.Cache; -import com.activeandroid.query.Delete; -import com.activeandroid.query.From; -import com.activeandroid.query.Select; -import com.activeandroid.util.SQLiteUtils; -import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; import java.io.IOException; import java.io.Writer; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.LinkedList; +import java.util.List; -public class ScoreList +public abstract class ScoreList { - @NonNull - private Habit habit; - public ModelObservable observable = new ModelObservable(); + protected final Habit habit; + + protected ModelObservable observable; /** - * Constructs a new ScoreList associated with the given habit. + * Creates a new ScoreList for the given habit. + *

+ * The list is populated automatically according to the repetitions that the + * habit has. * - * @param habit the habit this list should be associated with + * @param habit the habit to which the scores belong. */ - public ScoreList(@NonNull Habit habit) + public ScoreList(Habit habit) { this.habit = habit; - } - - protected From select() - { - return new Select() - .from(Score.class) - .where("habit = ?", habit.getId()) - .orderBy("timestamp desc"); + observable = new ModelObservable(); } /** - * Marks all scores that have timestamp equal to or newer than the given timestamp as invalid. - * Any following getValue calls will trigger the scores to be recomputed. + * Returns the values of all the scores, from day of the first repetition + * until today, grouped in chunks of specified size. + *

+ * If the group size is one, then the value of each score is returned + * individually. If the group is, for example, seven, then the days are + * grouped in groups of seven consecutive days. + *

+ * The values are returned in an array of integers, with one entry for each + * group of days in the interval. This value corresponds to the average of + * the scores for the days inside the group. The first entry corresponds to + * the ending of the interval (that is, the most recent group of days). The + * last entry corresponds to the beginning of the interval. As usual, the + * time of the day for the timestamps should be midnight (UTC). The + * endpoints of the interval are included. + *

+ * The values are returned in an integer array. There is one entry for each + * day inside the interval. The first entry corresponds to today, while the + * last entry corresponds to the day of the oldest repetition. * - * @param timestamp the oldest timestamp that should be invalidated + * @param divisor the size of the groups + * @return array of values, with one entry for each group of days */ - public void invalidateNewerThan(long timestamp) + @NonNull + public int[] getAllValues(long divisor) { - new Delete().from(Score.class) - .where("habit = ?", habit.getId()) - .and("timestamp >= ?", timestamp) - .execute(); + Repetition oldestRep = habit.getRepetitions().getOldest(); + if (oldestRep == null) return new int[0]; - observable.notifyListeners(); + long fromTimestamp = oldestRep.getTimestamp(); + long toTimestamp = DateUtils.getStartOfToday(); + return getValues(fromTimestamp, toTimestamp, divisor); } - /** - * Computes and saves the scores that are missing since the first repetition of the habit. - */ - private void computeAll() + public ModelObservable getObservable() { - long fromTimestamp = habit.repetitions.getOldestTimestamp(); - if(fromTimestamp == 0) return; - - long toTimestamp = DateUtils.getStartOfToday(); - compute(fromTimestamp, toTimestamp); + return observable; } /** - * Computes and saves the scores that are missing inside a given time interval. Scores that - * have already been computed are skipped, therefore there is no harm in calling this function - * more times, or with larger intervals, than strictly needed. The endpoints of the interval are - * included. - * - * This function assumes that there are no gaps on the scores. That is, if the newest score has - * timestamp t, then every score with timestamp lower than t has already been computed. + * Returns the value of the score for today. * - * @param from timestamp of the beginning of the interval - * @param to timestamp of the end of the time interval + * @return value of today's score */ - protected void compute(long from, long to) + public int getTodayValue() { - InterfaceUtils.throwIfMainThread(); - - final long day = DateUtils.millisecondsInOneDay; - final double freq = ((double) habit.freqNum) / habit.freqDen; - - int newestScoreValue = findNewestValue(); - long newestTimestamp = findNewestTimestamp(); - - if(newestTimestamp > 0) - from = newestTimestamp + day; - - final int checkmarkValues[] = habit.checkmarks.getValues(from, to); - final long beginning = from; - - int lastScore = newestScoreValue; - int size = checkmarkValues.length; - - long timestamps[] = new long[size]; - long values[] = new long[size]; - - for (int i = 0; i < checkmarkValues.length; i++) - { - int checkmarkValue = checkmarkValues[checkmarkValues.length - i - 1]; - lastScore = Score.compute(freq, lastScore, checkmarkValue); - timestamps[i] = beginning + day * i; - values[i] = lastScore; - } - - insert(timestamps, values); + return getValue(DateUtils.getStartOfToday()); } /** - * Returns the value of the most recent score that was already computed. If no score has been - * computed yet, returns zero. + * Returns the value of the score for a given day. + * + * @param timestamp the timestamp of a day + * @return score for that day + */ + public abstract int getValue(long timestamp); + + /** + * Marks all scores that have timestamp equal to or newer than the given + * timestamp as invalid. Any following getValue calls will trigger the + * scores to be recomputed. * - * @return value of newest score, or zero if none exist + * @param timestamp the oldest timestamp that should be invalidated */ - protected int findNewestValue() - { - String args[] = { habit.getId().toString() }; - String query = "select score from Score where habit = ? order by timestamp desc limit 1"; - return SQLiteUtils.intQuery(query, args); - } + public abstract void invalidateNewerThan(long timestamp); - private long findNewestTimestamp() + public void writeCSV(Writer out) throws IOException { - String args[] = { habit.getId().toString() }; - String query = "select timestamp from Score where habit = ? order by timestamp desc limit 1"; - return DatabaseUtils.longQuery(query, args); - } + computeAll(); - private void insert(long timestamps[], long values[]) - { - String query = "insert into Score(habit, timestamp, score) values (?,?,?)"; + SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); - SQLiteDatabase db = Cache.openDatabase(); - db.beginTransaction(); + String query = + "select timestamp, score from score where habit = ? order by timestamp"; + String params[] = {habit.getId().toString()}; - try - { - SQLiteStatement statement = db.compileStatement(query); + SQLiteDatabase db = Cache.openDatabase(); + Cursor cursor = db.rawQuery(query, params); - for (int i = 0; i < timestamps.length; i++) - { - statement.bindLong(1, habit.getId()); - statement.bindLong(2, timestamps[i]); - statement.bindLong(3, values[i]); - statement.execute(); - } + if (!cursor.moveToFirst()) return; - db.setTransactionSuccessful(); - } - finally + do { - db.endTransaction(); - } - } - - /** - * Returns the score for a certain day. - * - * @param timestamp the timestamp for the day - * @return the score for the day - */ - @Nullable - protected Score get(long timestamp) - { - Repetition oldestRep = habit.repetitions.getOldest(); - if(oldestRep == null) return null; - - compute(oldestRep.timestamp, timestamp); + String timestamp = dateFormat.format(new Date(cursor.getLong(0))); + String score = String.format("%.4f", + ((float) cursor.getInt(1)) / Score.MAX_VALUE); + out.write(String.format("%s,%s\n", timestamp, score)); - return select().where("timestamp = ?", timestamp).executeSingle(); - } + } while (cursor.moveToNext()); - /** - * Returns the value of the score for a given day. - * - * @param timestamp the timestamp of a day - * @return score for that day - */ - public int getValue(long timestamp) - { - computeAll(); - String[] args = { habit.getId().toString(), Long.toString(timestamp) }; - return SQLiteUtils.intQuery("select score from Score where habit = ? and timestamp = ?", args); + cursor.close(); + out.close(); } - /** - * Returns the values of all the scores, from day of the first repetition until today, grouped - * in chunks of specified size. - * - * If the group size is one, then the value of each score is returned individually. If the group - * is, for example, seven, then the days are grouped in groups of seven consecutive days. - * - * The values are returned in an array of integers, with one entry for each group of days in the - * interval. This value corresponds to the average of the scores for the days inside the group. - * The first entry corresponds to the ending of the interval (that is, the most recent group of - * days). The last entry corresponds to the beginning of the interval. As usual, the time of the - * day for the timestamps should be midnight (UTC). The endpoints of the interval are included. - * - * The values are returned in an integer array. There is one entry for each day inside the - * interval. The first entry corresponds to today, while the last entry corresponds to the - * day of the oldest repetition. - * - * @param divisor the size of the groups - * @return array of values, with one entry for each group of days - */ - @NonNull - public int[] getAllValues(long divisor) - { - Repetition oldestRep = habit.repetitions.getOldest(); - if(oldestRep == null) return new int[0]; - - long fromTimestamp = oldestRep.timestamp; - long toTimestamp = DateUtils.getStartOfToday(); - return getValues(fromTimestamp, toTimestamp, divisor); - } + protected abstract void add(List scores); /** - * Same as getAllValues(long), but using a specified interval. + * Computes and saves the scores that are missing inside a given time + * interval. + *

+ * Scores that have already been computed are skipped, therefore there is no + * harm in calling this function more times, or with larger intervals, than + * strictly needed. The endpoints of the interval are included. + *

+ * This function assumes that there are no gaps on the scores. That is, if + * the newest score has timestamp t, then every score with timestamp lower + * than t has already been computed. * - * @param from beginning of the interval (included) - * @param to end of the interval (included) - * @param divisor size of the groups - * @return array of values, with one entry for each group of days + * @param from timestamp of the beginning of the interval + * @param to timestamp of the end of the time interval */ - @NonNull - protected int[] getValues(long from, long to, long divisor) + protected void compute(long from, long to) { - compute(from, to); + final long day = DateUtils.millisecondsInOneDay; + final double freq = ((double) habit.getFreqNum()) / habit.getFreqDen(); - divisor *= DateUtils.millisecondsInOneDay; - Long offset = to + divisor; + int newestValue = 0; + long newestTimestamp = 0; - String query = "select ((timestamp - ?) / ?) as time, avg(score) from Score " + - "where habit = ? and timestamp >= ? and timestamp <= ? " + - "group by time order by time desc"; + Score newest = getNewestComputed(); + if(newest != null) + { + newestValue = newest.getValue(); + newestTimestamp = newest.getTimestamp(); + } - String params[] = { offset.toString(), Long.toString(divisor), habit.getId().toString(), - Long.toString(from), Long.toString(to) }; + if (newestTimestamp > 0) from = newestTimestamp + day; - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); - - if(!cursor.moveToFirst()) return new int[0]; + final int checkmarkValues[] = habit.getCheckmarks().getValues(from, to); + final long beginning = from; - int k = 0; - int[] scores = new int[cursor.getCount()]; + int lastScore = newestValue; + List scores = new LinkedList<>(); - do + for (int i = 0; i < checkmarkValues.length; i++) { - scores[k++] = (int) cursor.getFloat(1); + int value = checkmarkValues[checkmarkValues.length - i - 1]; + lastScore = Score.compute(freq, lastScore, value); + scores.add(new Score(habit, beginning + day * i, lastScore)); } - while (cursor.moveToNext()); - cursor.close(); - return scores; + add(scores); } /** - * Returns the score for today. - * - * @return score for today + * Computes and saves the scores that are missing since the first repetition + * of the habit. */ - @Nullable - protected Score getToday() + protected void computeAll() { - return get(DateUtils.getStartOfToday()); + Repetition oldestRep = habit.getRepetitions().getOldest(); + if (oldestRep == null) return; + + long toTimestamp = DateUtils.getStartOfToday(); + compute(oldestRep.getTimestamp(), toTimestamp); } /** - * Returns the value of the score for today. + * Returns the score for a certain day. * - * @return value of today's score + * @param timestamp the timestamp for the day + * @return the score for the day */ - public int getTodayValue() - { - return getValue(DateUtils.getStartOfToday()); - } + protected abstract Score get(long timestamp); /** - * Returns the star status for today. The returned value is either Score.EMPTY_STAR, - * Score.HALF_STAR or Score.FULL_STAR. + * Returns the most recent score that was already computed. + *

+ * If no score has been computed yet, returns null. * - * @return star status for today + * @return the newest score computed, or null if none exist */ - public int getTodayStarStatus() - { - Score score = getToday(); - if(score != null) return score.getStarStatus(); - else return Score.EMPTY_STAR; - } - - public void writeCSV(Writer out) throws IOException - { - computeAll(); - - SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); - - String query = "select timestamp, score from score where habit = ? order by timestamp"; - String params[] = { habit.getId().toString() }; - - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); - - if(!cursor.moveToFirst()) return; - - do - { - String timestamp = dateFormat.format(new Date(cursor.getLong(0))); - String score = String.format("%.4f", ((float) cursor.getInt(1)) / Score.MAX_VALUE); - out.write(String.format("%s,%s\n", timestamp, score)); - - } while(cursor.moveToNext()); + @Nullable + protected abstract Score getNewestComputed(); - cursor.close(); - out.close(); - } + /** + * Same as getAllValues(long), but using a specified interval. + * + * @param from beginning of the interval (included) + * @param to end of the interval (included) + * @param divisor size of the groups + * @return array of values, with one entry for each group of days + */ + protected abstract int[] getValues(long from, long to, long divisor); } diff --git a/app/src/main/java/org/isoron/uhabits/models/Streak.java b/app/src/main/java/org/isoron/uhabits/models/Streak.java index 35a20f444..53854a798 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Streak.java +++ b/app/src/main/java/org/isoron/uhabits/models/Streak.java @@ -19,20 +19,63 @@ package org.isoron.uhabits.models; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.isoron.uhabits.utils.DateUtils; -public class Streak extends Model +public class Streak { - @Column(name = "habit") - public Habit habit; + private Habit habit; - @Column(name = "start") - public Long start; + private long start; - @Column(name = "end") - public Long end; + private long end; - @Column(name = "length") - public Long length; + public Streak(Habit habit, long start, long end) + { + this.habit = habit; + this.start = start; + this.end = end; + } + + public int compareLonger(Streak other) + { + if (this.getLength() != other.getLength()) + return Long.signum(this.getLength() - other.getLength()); + + return Long.signum(this.getEnd() - other.getEnd()); + } + + public int compareNewer(Streak other) + { + return Long.signum(this.getEnd() - other.getEnd()); + } + + public long getEnd() + { + return end; + } + + public Habit getHabit() + { + return habit; + } + + public long getLength() + { + return (end - start) / DateUtils.millisecondsInOneDay + 1; + } + + public long getStart() + { + return start; + } + + @Override + public String toString() + { + return new ToStringBuilder(this) + .append("start", start) + .append("end", end) + .toString(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java index 523b9e339..19fc4718c 100644 --- a/app/src/main/java/org/isoron/uhabits/models/StreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java @@ -19,100 +19,123 @@ package org.isoron.uhabits.models; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; - -import com.activeandroid.ActiveAndroid; -import com.activeandroid.Cache; -import com.activeandroid.query.Delete; -import com.activeandroid.query.Select; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; -public class StreakList +/** + * The collection of {@link Streak}s that belong to a habit. + *

+ * This list is populated automatically from the list of repetitions. + */ +public abstract class StreakList { - private Habit habit; - public ModelObservable observable = new ModelObservable(); + protected final Habit habit; + + protected ModelObservable observable; - public StreakList(Habit habit) + protected StreakList(Habit habit) { this.habit = habit; + observable = new ModelObservable(); } - public List getAll(int limit) + public abstract List getAll(); + + public List getBest(int limit) { - rebuild(); + List streaks = getAll(); + Collections.sort(streaks, (s1, s2) -> s2.compareLonger(s1)); + streaks = streaks.subList(0, Math.min(streaks.size(), limit)); + Collections.sort(streaks, (s1, s2) -> s2.compareNewer(s1)); + return streaks; + } - String query = "select * from (select * from streak where habit=? " + - "order by end <> ?, length desc, end desc limit ?) order by end desc"; + public abstract Streak getNewestComputed(); - String params[] = {habit.getId().toString(), Long.toString(DateUtils.getStartOfToday()), - Integer.toString(limit)}; + public ModelObservable getObservable() + { + return observable; + } - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); + public abstract void invalidateNewerThan(long timestamp); - if(!cursor.moveToFirst()) - { - cursor.close(); - return new LinkedList<>(); - } + public void rebuild() + { + long today = DateUtils.getStartOfToday(); - List streaks = new LinkedList<>(); + Long beginning = findBeginning(); + if (beginning == null || beginning > today) return; + + int checks[] = habit.getCheckmarks().getValues(beginning, today); + List streaks = checkmarksToStreaks(beginning, checks); - do + removeNewestComputed(); + insert(streaks); + } + + /** + * Converts a list of checkmark values to a list of streaks. + * + * @param beginning the timestamp corresponding to the first checkmark + * value. + * @param checks the checkmarks values, ordered by decreasing timestamp. + * @return the list of streaks. + */ + @NonNull + protected List checkmarksToStreaks(Long beginning, int[] checks) + { + ArrayList transitions = getTransitions(beginning, checks); + + List streaks = new LinkedList<>(); + for (int i = 0; i < transitions.size(); i += 2) { - Streak s = Streak.load(Streak.class, cursor.getInt(0)); - streaks.add(s); + long start = transitions.get(i); + long end = transitions.get(i + 1); + streaks.add(new Streak(habit, start, end)); } - while (cursor.moveToNext()); - cursor.close(); return streaks; - } - public Streak getNewest() + /** + * Finds the place where we should start when recomputing the streaks. + * + * @return + */ + @Nullable + protected Long findBeginning() { - return new Select().from(Streak.class) - .where("habit = ?", habit.getId()) - .orderBy("end desc") - .limit(1) - .executeSingle(); + Streak newestStreak = getNewestComputed(); + if (newestStreak != null) return newestStreak.getStart(); + + Repetition oldestRep = habit.getRepetitions().getOldest(); + if (oldestRep != null) return oldestRep.getTimestamp(); + + return null; } - public void rebuild() + /** + * Returns the timestamps where there was a transition from performing a + * habit to not performing a habit, and vice-versa. + * + * @param beginning the timestamp for the first checkmark + * @param checks the checkmarks, ordered by decresing timestamp + * @return the list of transitions + */ + @NonNull + protected ArrayList getTransitions(Long beginning, int[] checks) { - InterfaceUtils.throwIfMainThread(); - - long beginning; - long today = DateUtils.getStartOfToday(); long day = DateUtils.millisecondsInOneDay; + long current = beginning; - Streak newestStreak = getNewest(); - if (newestStreak != null) - { - beginning = newestStreak.start; - } - else - { - Repetition oldestRep = habit.repetitions.getOldest(); - if (oldestRep == null) return; - - beginning = oldestRep.timestamp; - } - - if (beginning > today) return; - - int checks[] = habit.checkmarks.getValues(beginning, today); ArrayList list = new ArrayList<>(); - - long current = beginning; list.add(current); for (int i = 1; i < checks.length; i++) @@ -126,38 +149,10 @@ public class StreakList if (list.size() % 2 == 1) list.add(current); - ActiveAndroid.beginTransaction(); - - if(newestStreak != null) newestStreak.delete(); - - try - { - for (int i = 0; i < list.size(); i += 2) - { - Streak streak = new Streak(); - streak.habit = habit; - streak.start = list.get(i); - streak.end = list.get(i + 1); - streak.length = (streak.end - streak.start) / day + 1; - streak.save(); - } - - ActiveAndroid.setTransactionSuccessful(); - } - finally - { - ActiveAndroid.endTransaction(); - } + return list; } + protected abstract void insert(List streaks); - public void deleteNewerThan(long timestamp) - { - new Delete().from(Streak.class) - .where("habit = ?", habit.getId()) - .and("end >= ?", timestamp - DateUtils.millisecondsInOneDay) - .execute(); - - observable.notifyListeners(); - } + protected abstract void removeNewestComputed(); } diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryCheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryCheckmarkList.java new file mode 100644 index 000000000..f42879deb --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryCheckmarkList.java @@ -0,0 +1,102 @@ +/* + * 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.memory; + +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.models.CheckmarkList; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.Collections; +import java.util.LinkedList; + +/** + * In-memory implementation of {@link CheckmarkList}. + */ +public class MemoryCheckmarkList extends CheckmarkList +{ + LinkedList list; + + public MemoryCheckmarkList(Habit habit) + { + super(habit); + list = new LinkedList<>(); + } + + @Override + public int[] getValues(long from, long to) + { + compute(from, to); + if (from > to) return new int[0]; + + int length = (int) ((to - from) / DateUtils.millisecondsInOneDay + 1); + int values[] = new int[length]; + + int k = 0; + for (Checkmark c : list) + if(c.getTimestamp() >= from && c.getTimestamp() <= to) + values[k++] = c.getValue(); + + return values; + } + + @Override + public void invalidateNewerThan(long timestamp) + { + LinkedList invalid = new LinkedList<>(); + + for (Checkmark c : list) + if (c.getTimestamp() >= timestamp) invalid.add(c); + + list.removeAll(invalid); + } + + @Override + protected Checkmark getNewest() + { + long newestTimestamp = 0; + Checkmark newestCheck = null; + + for (Checkmark c : list) + { + if (c.getTimestamp() > newestTimestamp) + { + newestCheck = c; + newestTimestamp = c.getTimestamp(); + } + } + + return newestCheck; + } + + @Override + protected void insert(long[] timestamps, int[] values) + { + for (int i = 0; i < timestamps.length; i++) + { + long t = timestamps[i]; + int v = values[i]; + list.add(new Checkmark(habit, t, v)); + } + + Collections.sort(list, + (c1, c2) -> (int) (c2.getTimestamp() - c1.getTimestamp())); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryHabitList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryHabitList.java new file mode 100644 index 000000000..deee26b0d --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryHabitList.java @@ -0,0 +1,111 @@ +/* + * 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.memory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; + +import java.util.LinkedList; +import java.util.List; + +/** + * In-memory implementation of {@link HabitList}. + */ +public class MemoryHabitList extends HabitList +{ + @NonNull + private LinkedList list; + + public MemoryHabitList() + { + list = new LinkedList<>(); + } + + @Override + public void add(Habit habit) + { + list.addLast(habit); + } + + @Override + public int count() + { + int count = 0; + for (Habit h : list) if (!h.isArchived()) count++; + return count; + } + + @Override + public int countWithArchived() + { + return list.size(); + } + + @Override + public Habit getById(long id) + { + for (Habit h : list) if (h.getId() == id) return h; + return null; + } + + @NonNull + @Override + public List getAll(boolean includeArchive) + { + if (includeArchive) return new LinkedList<>(list); + return getFiltered(habit -> !habit.isArchived()); + } + + @Nullable + @Override + public Habit getByPosition(int position) + { + return list.get(position); + } + + @Override + public int indexOf(Habit h) + { + return list.indexOf(h); + } + + @Override + public void remove(@NonNull Habit habit) + { + list.remove(habit); + } + + @Override + public void reorder(Habit from, Habit to) + { + int toPos = indexOf(to); + list.remove(from); + list.add(toPos, from); + } + + @Override + public void update(List habits) + { + // NOP + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java new file mode 100644 index 000000000..389d7f59e --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java @@ -0,0 +1,61 @@ +/* + * 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.memory; + +import org.isoron.uhabits.models.CheckmarkList; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.models.ModelFactory; +import org.isoron.uhabits.models.RepetitionList; +import org.isoron.uhabits.models.ScoreList; +import org.isoron.uhabits.models.StreakList; + +public class MemoryModelFactory implements ModelFactory +{ + @Override + public RepetitionList buidRepetitionList(Habit habit) + { + return new MemoryRepetitionList(habit); + } + + @Override + public HabitList buildHabitList() + { + return new MemoryHabitList(); + } + + @Override + public CheckmarkList buildCheckmarkList(Habit habit) + { + return new MemoryCheckmarkList(habit); + } + + @Override + public ScoreList buildScoreList(Habit habit) + { + return null; + } + + @Override + public StreakList buildStreakList(Habit habit) + { + return new MemoryStreakList(habit); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java new file mode 100644 index 000000000..13016e8f2 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java @@ -0,0 +1,106 @@ +/* + * 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.memory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Repetition; +import org.isoron.uhabits.models.RepetitionList; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * In-memory implementation of {@link RepetitionList}. + */ +public class MemoryRepetitionList extends RepetitionList +{ + LinkedList list; + + public MemoryRepetitionList(Habit habit) + { + super(habit); + list = new LinkedList<>(); + } + + @Override + public void add(Repetition repetition) + { + list.add(repetition); + observable.notifyListeners(); + } + + @Override + public List getByInterval(long fromTimestamp, long toTimestamp) + { + LinkedList filtered = new LinkedList<>(); + for (Repetition r : list) + { + long t = r.getTimestamp(); + if (t >= fromTimestamp && t <= toTimestamp) filtered.add(r); + } + + Collections.sort(filtered, + (r1, r2) -> (int) (r1.getTimestamp() - r2.getTimestamp())); + + return filtered; + } + + @Nullable + @Override + public Repetition getByTimestamp(long timestamp) + { + for (Repetition r : list) + if (r.getTimestamp() == timestamp) return r; + + return null; + } + + @Nullable + @Override + public Repetition getOldest() + { + long oldestTime = Long.MAX_VALUE; + Repetition oldestRep = null; + + for (Repetition rep : list) + { + if (rep.getTimestamp() < oldestTime) + { + oldestRep = rep; + oldestTime = rep.getTimestamp(); + } + + } + + return oldestRep; + } + + @Override + public void remove(@NonNull Repetition repetition) + { + list.remove(repetition); + observable.notifyListeners(); + } + +} diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryStreakList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryStreakList.java new file mode 100644 index 000000000..4efab4ecf --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryStreakList.java @@ -0,0 +1,86 @@ +/* + * 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.memory; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Streak; +import org.isoron.uhabits.models.StreakList; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class MemoryStreakList extends StreakList +{ + LinkedList list; + + public MemoryStreakList(Habit habit) + { + super(habit); + list = new LinkedList<>(); + } + + @Override + public Streak getNewestComputed() + { + Streak newest = null; + + for(Streak s : list) + if(newest == null || s.getEnd() > newest.getEnd()) + newest = s; + + return newest; + } + + @Override + public void invalidateNewerThan(long timestamp) + { + LinkedList discard = new LinkedList<>(); + + for(Streak s : list) + if(s.getEnd() >= timestamp - DateUtils.millisecondsInOneDay) + discard.add(s); + + list.removeAll(discard); + observable.notifyListeners(); + } + + @Override + protected void insert(List streaks) + { + list.addAll(streaks); + Collections.sort(list, (s1, s2) -> s2.compareNewer(s1)); + } + + @Override + protected void removeNewestComputed() + { + Streak newest = getNewestComputed(); + if(newest != null) list.remove(newest); + } + + @Override + public List getAll() + { + rebuild(); + return new LinkedList<>(list); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/package-info.java b/app/src/main/java/org/isoron/uhabits/models/memory/package-info.java new file mode 100644 index 000000000..f8272f334 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/memory/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides in-memory implementation of core models. + */ +package org.isoron.uhabits.models.memory; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/models/package-info.java b/app/src/main/java/org/isoron/uhabits/models/package-info.java new file mode 100644 index 000000000..9cb029b90 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/package-info.java @@ -0,0 +1,24 @@ +/* + * 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 Licenses along + * with this program. If not, see . + */ + +/** + * Provides core models classes, such as {@link org.isoron.uhabits.models.Habit} + * and {@link org.isoron.uhabits.models.Repetition}. + */ +package org.isoron.uhabits.models; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/CheckmarkRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/CheckmarkRecord.java new file mode 100644 index 000000000..a35d7a1c9 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/CheckmarkRecord.java @@ -0,0 +1,62 @@ +/* + * 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; + +import com.activeandroid.Model; +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; + +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.models.Habit; + +/** + * The SQLite database record corresponding to a {@link Checkmark}. + */ +@Table(name = "Checkmarks") +public class CheckmarkRecord extends Model +{ + /** + * The habit to which this checkmark belongs. + */ + @Column(name = "habit") + public HabitRecord habit; + + /** + * Timestamp of the day to which this checkmark corresponds. Time of the day + * must be midnight (UTC). + */ + @Column(name = "timestamp") + public Long timestamp; + + /** + * Indicates whether there is a repetition at the given timestamp or not, + * and whether the repetition was expected. Assumes one of the values + * UNCHECKED, CHECKED_EXPLICITLY or CHECKED_IMPLICITLY. + */ + @Column(name = "value") + public Integer value; + + public Checkmark toCheckmark() + { + SQLiteHabitList habitList = SQLiteHabitList.getInstance(); + Habit h = habitList.getById(habit.getId()); + return new Checkmark(h, timestamp, value); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/HabitRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/HabitRecord.java new file mode 100644 index 000000000..052739ef5 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/HabitRecord.java @@ -0,0 +1,177 @@ +/* + * 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; + +import android.annotation.SuppressLint; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.activeandroid.Model; +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; +import com.activeandroid.query.Delete; +import com.activeandroid.util.SQLiteUtils; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.DatabaseUtils; + +/** + * The SQLite database record corresponding to a {@link Habit}. + */ +@Table(name = "Habits") +public class HabitRecord extends Model +{ + public static final String HABIT_URI_FORMAT = + "content://org.isoron.uhabits/habit/%d"; + + @Column(name = "name") + public String name; + + @Column(name = "description") + public String description; + + @Column(name = "freq_num") + public Integer freqNum; + + @Column(name = "freq_den") + public Integer freqDen; + + @Column(name = "color") + public Integer color; + + @Column(name = "position") + public Integer position; + + @Nullable + @Column(name = "reminder_hour") + public Integer reminderHour; + + @Nullable + @Column(name = "reminder_min") + public Integer reminderMin; + + @NonNull + @Column(name = "reminder_days") + public Integer reminderDays; + + @Column(name = "highlight") + public Integer highlight; + + @Column(name = "archived") + public Integer archived; + + public HabitRecord() + { + + } + + @Nullable + public static HabitRecord get(Long id) + { + return HabitRecord.load(HabitRecord.class, id); + } + + /** + * Changes the id of a habit on the database. + * + * @param oldId the original id + * @param newId the new id + */ + @SuppressLint("DefaultLocale") + public static void updateId(long oldId, long newId) + { + SQLiteUtils.execSql( + String.format("update Habits set Id = %d where Id = %d", newId, + oldId)); + } + + /** + * Deletes the habit and all data associated to it, including checkmarks, + * repetitions and scores. + */ + public void cascadeDelete() + { + Long id = getId(); + + DatabaseUtils.executeAsTransaction(() -> { + new Delete() + .from(CheckmarkRecord.class) + .where("habit = ?", id) + .execute(); + + new Delete() + .from(RepetitionRecord.class) + .where("habit = ?", id) + .execute(); + + new Delete() + .from(ScoreRecord.class) + .where("habit = ?", id) + .execute(); + + new Delete() + .from(StreakRecord.class) + .where("habit = ?", id) + .execute(); + + delete(); + }); + } + + public void copyFrom(Habit model) + { + this.name = model.getName(); + this.description = model.getDescription(); + this.freqNum = model.getFreqNum(); + this.freqDen = model.getFreqDen(); + this.color = model.getColor(); + this.reminderHour = model.getReminderHour(); + this.reminderMin = model.getReminderMin(); + this.reminderDays = model.getReminderDays(); + this.highlight = model.getHighlight(); + this.archived = model.getArchived(); + } + + public void copyTo(Habit habit) + { + habit.setName(this.name); + habit.setDescription(this.description); + habit.setFreqNum(this.freqNum); + habit.setFreqDen(this.freqDen); + habit.setColor(this.color); + habit.setReminderHour(this.reminderHour); + habit.setReminderMin(this.reminderMin); + habit.setReminderDays(this.reminderDays); + habit.setHighlight(this.highlight); + habit.setArchived(this.archived); + habit.setId(this.getId()); + } + + /** + * Saves the habit on the database, and assigns the specified id to it. + * + * @param id the id that the habit should receive + */ + public void save(long id) + { + save(); + updateId(getId(), id); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/RepetitionRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/RepetitionRecord.java new file mode 100644 index 000000000..e29f792de --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/RepetitionRecord.java @@ -0,0 +1,58 @@ +/* + * 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; + +import com.activeandroid.Model; +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Repetition; + +/** + * The SQLite database record corresponding to a {@link Repetition}. + */ +@Table(name = "Repetitions") +public class RepetitionRecord extends Model +{ + @Column(name = "habit") + public HabitRecord habit; + + @Column(name = "timestamp") + public Long timestamp; + + public void copyFrom(Repetition repetition) + { + habit = HabitRecord.get(repetition.getHabit().getId()); + timestamp = repetition.getTimestamp(); + } + + public static RepetitionRecord get(Long id) + { + return RepetitionRecord.load(RepetitionRecord.class, id); + } + + public Repetition toRepetition() + { + SQLiteHabitList habitList = SQLiteHabitList.getInstance(); + Habit h = habitList.getById(habit.getId()); + return new Repetition(h, timestamp); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java new file mode 100644 index 000000000..48db5fe95 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java @@ -0,0 +1,64 @@ +/* + * 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; + +import org.isoron.uhabits.models.CheckmarkList; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.models.ModelFactory; +import org.isoron.uhabits.models.RepetitionList; +import org.isoron.uhabits.models.ScoreList; +import org.isoron.uhabits.models.StreakList; + +/** + * Factory that provides models backed by an SQLite database. + */ +public class SQLModelFactory implements ModelFactory +{ + @Override + public RepetitionList buidRepetitionList(Habit habit) + { + return new SQLiteRepetitionList(habit); + } + + @Override + public CheckmarkList buildCheckmarkList(Habit habit) + { + return new SQLiteCheckmarkList(habit); + } + + @Override + public HabitList buildHabitList() + { + return new SQLiteHabitList(); + } + + @Override + public ScoreList buildScoreList(Habit habit) + { + return new SQLiteScoreList(habit); + } + + @Override + public StreakList buildStreakList(Habit habit) + { + return new SQLiteStreakList(habit); + } +} 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 new file mode 100644 index 000000000..f3647e4d6 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkList.java @@ -0,0 +1,139 @@ +/* + * 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; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.activeandroid.Cache; +import com.activeandroid.query.Delete; +import com.activeandroid.query.Select; + +import org.isoron.uhabits.models.Checkmark; +import org.isoron.uhabits.models.CheckmarkList; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.DateUtils; + +/** + * Implementation of a {@link CheckmarkList} that is backed by SQLite. + */ +public class SQLiteCheckmarkList extends CheckmarkList +{ + public SQLiteCheckmarkList(Habit habit) + { + super(habit); + } + + @Override + public void invalidateNewerThan(long timestamp) + { + new Delete() + .from(CheckmarkRecord.class) + .where("habit = ?", habit.getId()) + .and("timestamp >= ?", timestamp) + .execute(); + + observable.notifyListeners(); + } + + @Override + @NonNull + public int[] getValues(long fromTimestamp, long toTimestamp) + { + compute(fromTimestamp, toTimestamp); + + if (fromTimestamp > toTimestamp) return new int[0]; + + String query = "select value, timestamp from Checkmarks where " + + "habit = ? and timestamp >= ? and timestamp <= ?"; + + SQLiteDatabase db = Cache.openDatabase(); + String args[] = { + habit.getId().toString(), + Long.toString(fromTimestamp), + Long.toString(toTimestamp) + }; + Cursor cursor = db.rawQuery(query, args); + + long day = DateUtils.millisecondsInOneDay; + int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1; + int[] checks = new int[nDays]; + + if (cursor.moveToFirst()) + { + do + { + long timestamp = cursor.getLong(1); + int offset = (int) ((timestamp - fromTimestamp) / day); + checks[nDays - offset - 1] = cursor.getInt(0); + + } while (cursor.moveToNext()); + } + + cursor.close(); + return checks; + } + + @Override + @Nullable + protected Checkmark getNewest() + { + CheckmarkRecord record = new Select() + .from(CheckmarkRecord.class) + .where("habit = ?", habit.getId()) + .and("timestamp <= ?", DateUtils.getStartOfToday()) + .orderBy("timestamp desc") + .limit(1) + .executeSingle(); + + return record.toCheckmark(); + } + + @Override + protected void insert(long timestamps[], int values[]) + { + String query = + "insert into Checkmarks(habit, timestamp, value) values (?,?,?)"; + + SQLiteDatabase db = Cache.openDatabase(); + db.beginTransaction(); + try + { + SQLiteStatement statement = db.compileStatement(query); + + for (int i = 0; i < timestamps.length; i++) + { + statement.bindLong(1, habit.getId()); + statement.bindLong(2, timestamps[i]); + statement.bindLong(3, values[i]); + statement.execute(); + } + + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java new file mode 100644 index 000000000..2a8f33b91 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java @@ -0,0 +1,232 @@ +/* + * 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; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.activeandroid.query.From; +import com.activeandroid.query.Select; +import com.activeandroid.query.Update; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * Implementation of a {@link HabitList} that is backed by SQLite. + */ +public class SQLiteHabitList extends HabitList +{ + private static SQLiteHabitList instance; + + private HashMap cache; + + public SQLiteHabitList() + { + cache = new HashMap<>(); + } + + public static SQLiteHabitList getInstance() + { + if (instance == null) instance = new SQLiteHabitList(); + return instance; + } + + @Override + public void add(Habit habit) + { + if(cache.containsValue(habit)) + throw new RuntimeException("habit already in cache"); + + HabitRecord record = new HabitRecord(); + record.copyFrom(habit); + record.position = countWithArchived(); + + Long id = habit.getId(); + if(id == null) id = record.save(); + else record.save(id); + + habit.setId(id); + cache.put(id, habit); + } + + @Override + public int count() + { + return select().count(); + } + + @Override + public int countWithArchived() + { + return selectWithArchived().count(); + } + + @Override + @NonNull + public List getAll(boolean includeArchive) + { + List recordList; + if (includeArchive) recordList = selectWithArchived().execute(); + else recordList = select().execute(); + + List habits = new LinkedList<>(); + for (HabitRecord record : recordList) + { + Habit habit = getById(record.getId()); + if (habit == null) + throw new RuntimeException("habit not in database"); + habits.add(habit); + } + + return habits; + } + + @Override + @Nullable + public Habit getById(long id) + { + if (!cache.containsKey(id)) + { + HabitRecord record = HabitRecord.get(id); + if (record == null) return null; + + Habit habit = new Habit(); + record.copyTo(habit); + cache.put(id, habit); + } + + return cache.get(id); + } + + @Override + @Nullable + public Habit getByPosition(int position) + { + HabitRecord record = selectWithArchived() + .where("position = ?", position) + .executeSingle(); + + return getById(record.getId()); + } + + @Override + public int indexOf(Habit h) + { + HabitRecord record = HabitRecord.get(h.getId()); + if (record == null) return -1; + return record.position; + } + + @Deprecated + public void rebuildOrder() + { + List habits = getAll(true); + + int i = 0; + for (Habit h : habits) + { + HabitRecord record = HabitRecord.get(h.getId()); + if (record == null) + throw new RuntimeException("habit not in database"); + + record.position = i++; + record.save(); + } + + update(habits); + } + + @Override + public void remove(@NonNull Habit habit) + { + if (!cache.containsKey(habit.getId())) + throw new RuntimeException("habit not in cache"); + + cache.remove(habit.getId()); + HabitRecord record = HabitRecord.get(habit.getId()); + if (record == null) throw new RuntimeException("habit not in database"); + record.cascadeDelete(); + rebuildOrder(); + } + + @Override + public void reorder(Habit from, Habit to) + { + if (from == to) return; + + Integer toPos = indexOf(to); + Integer fromPos = indexOf(from); + + if (toPos < fromPos) + { + new Update(HabitRecord.class) + .set("position = position + 1") + .where("position >= ? and position < ?", toPos, fromPos) + .execute(); + } + else + { + new Update(HabitRecord.class) + .set("position = position - 1") + .where("position > ? and position <= ?", fromPos, toPos) + .execute(); + } + + HabitRecord record = HabitRecord.get(from.getId()); + if (record == null) throw new RuntimeException("habit not in database"); + record.position = toPos; + record.save(); + + update(from); + } + + @Override + public void update(List habits) + { + for (Habit h : habits) + { + HabitRecord record = HabitRecord.get(h.getId()); + if (record == null) + throw new RuntimeException("habit not in database"); + record.copyFrom(h); + record.save(); + } + } + + @NonNull + private From select() + { + return new Select() + .from(HabitRecord.class) + .where("archived = 0") + .orderBy("position"); + } + + @NonNull + private From selectWithArchived() + { + return new Select().from(HabitRecord.class).orderBy("position"); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java new file mode 100644 index 000000000..ea468d32c --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java @@ -0,0 +1,143 @@ +/* + * 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; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.activeandroid.query.Delete; +import com.activeandroid.query.From; +import com.activeandroid.query.Select; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Repetition; +import org.isoron.uhabits.models.RepetitionList; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * Implementation of a {@link RepetitionList} that is backed by SQLite. + */ +public class SQLiteRepetitionList extends RepetitionList +{ + HashMap cache; + + public SQLiteRepetitionList(@NonNull Habit habit) + { + super(habit); + this.cache = new HashMap<>(); + } + + @Override + public void add(Repetition rep) + { + RepetitionRecord record = new RepetitionRecord(); + record.copyFrom(rep); + long id = record.save(); + cache.put(id, rep); + observable.notifyListeners(); + } + + @Override + public List getByInterval(long timeFrom, long timeTo) + { + return getFromRecord(selectFromTo(timeFrom, timeTo).execute()); + } + + @Override + public Repetition getByTimestamp(long timestamp) + { + RepetitionRecord record = + select().where("timestamp = ?", timestamp).executeSingle(); + return getFromRecord(record); + } + + @Override + public Repetition getOldest() + { + RepetitionRecord record = select().limit(1).executeSingle(); + return getFromRecord(record); + } + + @Override + public void remove(@NonNull Repetition repetition) + { + new Delete() + .from(RepetitionRecord.class) + .where("habit = ?", habit.getId()) + .and("timestamp = ?", repetition.getTimestamp()) + .execute(); + + observable.notifyListeners(); + } + + @NonNull + private List getFromRecord( + @Nullable List records) + { + List reps = new LinkedList<>(); + if (records == null) return reps; + + for (RepetitionRecord record : records) + { + Repetition rep = getFromRecord(record); + reps.add(rep); + } + + return reps; + } + + @Nullable + private Repetition getFromRecord(@Nullable RepetitionRecord record) + { + if (record == null) return null; + + Long id = record.getId(); + + if (!cache.containsKey(id)) + { + Repetition repetition = record.toRepetition(); + cache.put(id, repetition); + } + + return cache.get(id); + } + + @NonNull + private From select() + { + return new Select() + .from(RepetitionRecord.class) + .where("habit = ?", habit.getId()) + .and("timestamp <= ?", DateUtils.getStartOfToday()) + .orderBy("timestamp"); + } + + @NonNull + private From selectFromTo(long timeFrom, long timeTo) + { + return select() + .and("timestamp >= ?", timeFrom) + .and("timestamp <= ?", timeTo); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java new file mode 100644 index 000000000..e8d1603f3 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java @@ -0,0 +1,173 @@ +/* + * 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; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.activeandroid.Cache; +import com.activeandroid.query.Delete; +import com.activeandroid.query.From; +import com.activeandroid.query.Select; +import com.activeandroid.util.SQLiteUtils; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Repetition; +import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.models.ScoreList; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.List; + +/** + * Implementation of a ScoreList that is backed by SQLite. + */ +public class SQLiteScoreList extends ScoreList +{ + /** + * Constructs a new ScoreList associated with the given habit. + * + * @param habit the habit this list should be associated with + */ + public SQLiteScoreList(@NonNull Habit habit) + { + super(habit); + } + + @Override + public int getValue(long timestamp) + { + computeAll(); + String[] args = {habit.getId().toString(), Long.toString(timestamp)}; + return SQLiteUtils.intQuery( + "select score from Score where habit = ? and timestamp = ?", args); + } + + @Override + public void invalidateNewerThan(long timestamp) + { + new Delete() + .from(ScoreRecord.class) + .where("habit = ?", habit.getId()) + .and("timestamp >= ?", timestamp) + .execute(); + } + + @Nullable + @Override + protected Score getNewestComputed() + { + ScoreRecord record = select().limit(1).executeSingle(); + return record.toScore(); + } + + @Override + @Nullable + protected Score get(long timestamp) + { + Repetition oldestRep = habit.getRepetitions().getOldest(); + if (oldestRep == null) return null; + compute(oldestRep.getTimestamp(), timestamp); + + ScoreRecord record = + select().where("timestamp = ?", timestamp).executeSingle(); + + return record.toScore(); + } + + @Override + @NonNull + protected int[] getValues(long from, long to, long divisor) + { + compute(from, to); + + divisor *= DateUtils.millisecondsInOneDay; + Long offset = to + divisor; + + String query = + "select ((timestamp - ?) / ?) as time, avg(score) from Score " + + "where habit = ? and timestamp >= ? and timestamp <= ? " + + "group by time order by time desc"; + + String params[] = { + offset.toString(), + Long.toString(divisor), + habit.getId().toString(), + Long.toString(from), + Long.toString(to) + }; + + SQLiteDatabase db = Cache.openDatabase(); + Cursor cursor = db.rawQuery(query, params); + + if (!cursor.moveToFirst()) return new int[0]; + + int k = 0; + int[] scores = new int[cursor.getCount()]; + + do + { + scores[k++] = (int) cursor.getFloat(1); + } while (cursor.moveToNext()); + + cursor.close(); + return scores; + } + + @Override + protected void add(List scores) + { + String query = + "insert into Score(habit, timestamp, score) values (?,?,?)"; + + SQLiteDatabase db = Cache.openDatabase(); + db.beginTransaction(); + + try + { + SQLiteStatement statement = db.compileStatement(query); + + for (Score s : scores) + { + statement.bindLong(1, habit.getId()); + statement.bindLong(2, s.getTimestamp()); + statement.bindLong(3, s.getValue()); + statement.execute(); + } + + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + } + } + + protected From select() + { + return new Select() + .from(ScoreRecord.class) + .where("habit = ?", habit.getId()) + .orderBy("timestamp desc"); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java new file mode 100644 index 000000000..e44daa300 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java @@ -0,0 +1,115 @@ +/* + * 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; + +import com.activeandroid.query.Delete; +import com.activeandroid.query.Select; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Streak; +import org.isoron.uhabits.models.StreakList; +import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.utils.DateUtils; + +import java.util.LinkedList; +import java.util.List; + +/** + * Implementation of a StreakList that is backed by SQLite. + */ +public class SQLiteStreakList extends StreakList +{ + public SQLiteStreakList(Habit habit) + { + super(habit); + } + + @Override + public List getAll() + { + rebuild(); + List records = new Select() + .from(StreakRecord.class) + .where("habit = ?", habit.getId()) + .orderBy("end desc") + .execute(); + + return recordsToStreaks(records); + } + + @Override + public Streak getNewestComputed() + { + rebuild(); + return getNewestRecord().toStreak(); + } + + @Override + public void invalidateNewerThan(long timestamp) + { + new Delete() + .from(StreakRecord.class) + .where("habit = ?", habit.getId()) + .and("end >= ?", timestamp - DateUtils.millisecondsInOneDay) + .execute(); + + observable.notifyListeners(); + } + + private StreakRecord getNewestRecord() + { + return new Select() + .from(StreakRecord.class) + .where("habit = ?", habit.getId()) + .orderBy("end desc") + .limit(1) + .executeSingle(); + } + + @Override + protected void insert(List streaks) + { + DatabaseUtils.executeAsTransaction(() -> { + for (Streak streak : streaks) + { + StreakRecord record = new StreakRecord(); + record.copyFrom(streak); + record.save(); + } + }); + } + + private List recordsToStreaks(List records) + { + LinkedList streaks = new LinkedList<>(); + + for (StreakRecord record : records) + streaks.add(record.toStreak()); + + return streaks; + } + + @Override + protected void removeNewestComputed() + { + StreakRecord newestStreak = getNewestRecord(); + if (newestStreak != null) newestStreak.delete(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/ScoreRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/ScoreRecord.java new file mode 100644 index 000000000..2efc2e21e --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/ScoreRecord.java @@ -0,0 +1,65 @@ +/* + * 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; + +import com.activeandroid.Model; +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Score; + +/** + * The SQLite database record corresponding to a Score. + */ +@Table(name = "Score") +public class ScoreRecord extends Model +{ + /** + * Habit to which this score belongs to. + */ + @Column(name = "habit") + public HabitRecord habit; + + /** + * Timestamp of the day to which this score applies. Time of day should be + * midnight (UTC). + */ + @Column(name = "timestamp") + public Long timestamp; + + /** + * Value of the score. + */ + @Column(name = "score") + public Integer score; + + /** + * Constructs and returns a {@link Score} based on this record's data. + * + * @return a {@link Score} with this record's data + */ + public Score toScore() + { + SQLiteHabitList habitList = SQLiteHabitList.getInstance(); + Habit h = habitList.getById(habit.getId()); + return new Score(h, timestamp, score); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/StreakRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/StreakRecord.java new file mode 100644 index 000000000..fd4229073 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/StreakRecord.java @@ -0,0 +1,66 @@ +/* + * 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; + +import com.activeandroid.Model; +import com.activeandroid.annotation.Column; +import com.activeandroid.annotation.Table; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Streak; + +/** + * The SQLite database record corresponding to a Streak. + */ +@Table(name = "Streak") +public class StreakRecord extends Model +{ + @Column(name = "habit") + public HabitRecord habit; + + @Column(name = "start") + public Long start; + + @Column(name = "end") + public Long end; + + @Column(name = "length") + public Long length; + + public static StreakRecord get(Long id) + { + return StreakRecord.load(StreakRecord.class, id); + } + + public void copyFrom(Streak streak) + { + habit = HabitRecord.get(streak.getHabit().getId()); + start = streak.getStart(); + end = streak.getEnd(); + length = streak.getLength(); + } + + public Streak toStreak() + { + SQLiteHabitList habitList = SQLiteHabitList.getInstance(); + Habit h = habitList.getById(habit.getId()); + return new Streak(h, start, end); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/package-info.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/package-info.java new file mode 100644 index 000000000..469894365 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides SQLite implementations of the core models. + */ +package org.isoron.uhabits.models.sqlite; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/package-info.java b/app/src/main/java/org/isoron/uhabits/package-info.java new file mode 100644 index 000000000..b080842fc --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides classes for the Loop Habit Tracker app. + */ +package org.isoron.uhabits; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/tasks/package-info.java b/app/src/main/java/org/isoron/uhabits/tasks/package-info.java new file mode 100644 index 000000000..cc837895e --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/tasks/package-info.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +/** + * Provides async tasks for useful operations such as {@link + * org.isoron.uhabits.tasks.ExportCSVTask}. + */ +package org.isoron.uhabits.tasks; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java index 8cd9d07e4..8dbb04681 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java @@ -25,6 +25,8 @@ import android.support.annotation.NonNull; import android.view.WindowManager; import org.isoron.uhabits.BuildConfig; +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.FileUtils; @@ -38,13 +40,19 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.LinkedList; +import javax.inject.Inject; + public class BaseSystem { private Context context; + @Inject + HabitList habitList; + public BaseSystem(Context context) { this.context = context; + HabitsApplication.getComponent().inject(this); } public String getLogcat() throws IOException @@ -146,7 +154,7 @@ public class BaseSystem @Override protected void doInBackground() { - ReminderUtils.createReminderAlarms(context); + ReminderUtils.createReminderAlarms(context, habitList); } }.execute(); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java index bfdb10c80..483cc1927 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java +++ b/app/src/main/java/org/isoron/uhabits/ui/about/package-info.java @@ -18,6 +18,6 @@ */ /** - * Contains classes for AboutActivity + * Provides activity that shows information about the app. */ package org.isoron.uhabits.ui.about; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java index 7dfb86e31..aecb2a5e4 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java @@ -85,8 +85,8 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment if (position < 0 || position > 4) throw new IllegalArgumentException(); int freqNums[] = {1, 1, 2, 5, 3}; int freqDens[] = {1, 7, 7, 7, 7}; - modifiedHabit.freqNum = freqNums[position]; - modifiedHabit.freqDen = freqDens[position]; + modifiedHabit.setFreqNum(freqNums[position]); + modifiedHabit.setFreqDen(freqDens[position]); helper.populateFrequencyFields(modifiedHabit); } @@ -95,12 +95,12 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putInt("color", modifiedHabit.color); + outState.putInt("color", modifiedHabit.getColor()); if (modifiedHabit.hasReminder()) { - outState.putInt("reminderMin", modifiedHabit.reminderMin); - outState.putInt("reminderHour", modifiedHabit.reminderHour); - outState.putInt("reminderDays", modifiedHabit.reminderDays); + outState.putInt("reminderMin", modifiedHabit.getReminderMin()); + outState.putInt("reminderHour", modifiedHabit.getReminderHour()); + outState.putInt("reminderDays", modifiedHabit.getReminderDays()); } } @@ -123,8 +123,8 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment if (modifiedHabit.hasReminder()) { - defaultHour = modifiedHabit.reminderHour; - defaultMin = modifiedHabit.reminderMin; + defaultHour = modifiedHabit.getReminderHour(); + defaultMin = modifiedHabit.getReminderMin(); } showTimePicker(defaultHour, defaultMin); @@ -147,18 +147,19 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment WeekdayPickerDialog dialog = new WeekdayPickerDialog(); dialog.setListener(new OnWeekdaysPickedListener()); dialog.setSelectedDays( - DateUtils.unpackWeekdayList(modifiedHabit.reminderDays)); + DateUtils.unpackWeekdayList(modifiedHabit.getReminderDays())); dialog.show(getFragmentManager(), "weekdayPicker"); } protected void restoreSavedInstance(@Nullable Bundle bundle) { if (bundle == null) return; - modifiedHabit.color = bundle.getInt("color", modifiedHabit.color); - modifiedHabit.reminderMin = bundle.getInt("reminderMin", -1); - modifiedHabit.reminderHour = bundle.getInt("reminderHour", -1); - modifiedHabit.reminderDays = bundle.getInt("reminderDays", -1); - if (modifiedHabit.reminderMin < 0) modifiedHabit.clearReminder(); + modifiedHabit.setColor( + bundle.getInt("color", modifiedHabit.getColor())); + modifiedHabit.setReminderMin(bundle.getInt("reminderMin", -1)); + modifiedHabit.setReminderHour(bundle.getInt("reminderHour", -1)); + modifiedHabit.setReminderDays(bundle.getInt("reminderDays", -1)); + if (modifiedHabit.getReminderMin() < 0) modifiedHabit.clearReminder(); } protected abstract void saveHabit(); @@ -167,7 +168,7 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment void showColorPicker() { int androidColor = - ColorUtils.getColor(getContext(), modifiedHabit.color); + ColorUtils.getColor(getContext(), modifiedHabit.getColor()); ColorPickerDialog picker = ColorPickerDialog.newInstance(R.string.color_picker_default_title, @@ -196,7 +197,7 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment int paletteColor = ColorUtils.colorToPaletteIndex(getActivity(), androidColor); prefs.setDefaultHabitColor(paletteColor); - modifiedHabit.color = paletteColor; + modifiedHabit.setColor(paletteColor); helper.populateColor(paletteColor); } } @@ -214,9 +215,9 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment @Override public void onTimeSet(RadialPickerLayout view, int hour, int minute) { - modifiedHabit.reminderHour = hour; - modifiedHabit.reminderMin = minute; - modifiedHabit.reminderDays = DateUtils.ALL_WEEK_DAYS; + modifiedHabit.setReminderHour(hour); + modifiedHabit.setReminderMin(minute); + modifiedHabit.setReminderDays(DateUtils.ALL_WEEK_DAYS); helper.populateReminderFields(modifiedHabit); } } @@ -229,8 +230,8 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment { if (isSelectionEmpty(selectedDays)) Arrays.fill(selectedDays, true); - modifiedHabit.reminderDays = - DateUtils.packWeekdayList(selectedDays); + modifiedHabit.setReminderDays( + DateUtils.packWeekdayList(selectedDays)); helper.populateReminderFields(modifiedHabit); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java index a9f518b48..993ba6839 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java @@ -73,12 +73,12 @@ public class BaseDialogHelper void parseFormIntoHabit(Habit habit) { - habit.name = tvName.getText().toString().trim(); - habit.description = tvDescription.getText().toString().trim(); + habit.setName(tvName.getText().toString().trim()); + habit.setDescription(tvDescription.getText().toString().trim()); String freqNum = tvFreqNum.getText().toString(); String freqDen = tvFreqDen.getText().toString(); - if (!freqNum.isEmpty()) habit.freqNum = Integer.parseInt(freqNum); - if (!freqDen.isEmpty()) habit.freqDen = Integer.parseInt(freqDen); + if (!freqNum.isEmpty()) habit.setFreqNum(Integer.parseInt(freqNum)); + if (!freqDen.isEmpty()) habit.setFreqDen(Integer.parseInt(freqDen)); } void populateColor(int paletteColor) @@ -89,10 +89,11 @@ public class BaseDialogHelper protected void populateForm(final Habit habit) { - if (habit.name != null) tvName.setText(habit.name); - if (habit.description != null) tvDescription.setText(habit.description); + if (habit.getName() != null) tvName.setText(habit.getName()); + if (habit.getDescription() != null) tvDescription.setText( + habit.getDescription()); - populateColor(habit.color); + populateColor(habit.getColor()); populateFrequencyFields(habit); populateReminderFields(habit); } @@ -102,15 +103,15 @@ public class BaseDialogHelper { int quickSelectPosition = -1; - if (habit.freqNum.equals(habit.freqDen)) quickSelectPosition = 0; + if (habit.getFreqNum().equals(habit.getFreqDen())) quickSelectPosition = 0; - else if (habit.freqNum == 1 && habit.freqDen == 7) + else if (habit.getFreqNum() == 1 && habit.getFreqDen() == 7) quickSelectPosition = 1; - else if (habit.freqNum == 2 && habit.freqDen == 7) + else if (habit.getFreqNum() == 2 && habit.getFreqDen() == 7) quickSelectPosition = 2; - else if (habit.freqNum == 5 && habit.freqDen == 7) + else if (habit.getFreqNum() == 5 && habit.getFreqDen() == 7) quickSelectPosition = 3; if (quickSelectPosition >= 0) @@ -118,8 +119,8 @@ public class BaseDialogHelper else showCustomFrequency(); - tvFreqNum.setText(habit.freqNum.toString()); - tvFreqDen.setText(habit.freqDen.toString()); + tvFreqNum.setText(habit.getFreqNum().toString()); + tvFreqDen.setText(habit.getFreqDen().toString()); } @SuppressWarnings("ConstantConditions") @@ -133,12 +134,13 @@ public class BaseDialogHelper } String time = - DateUtils.formatTime(frag.getContext(), habit.reminderHour, - habit.reminderMin); + DateUtils.formatTime(frag.getContext(), habit.getReminderHour(), + habit.getReminderMin()); tvReminderTime.setText(time); llReminderDays.setVisibility(View.VISIBLE); - boolean weekdays[] = DateUtils.unpackWeekdayList(habit.reminderDays); + boolean weekdays[] = DateUtils.unpackWeekdayList( + habit.getReminderDays()); tvReminderDays.setText( DateUtils.formatWeekdayList(frag.getContext(), weekdays)); } @@ -161,21 +163,21 @@ public class BaseDialogHelper { Boolean valid = true; - if (habit.name.length() == 0) + if (habit.getName().length() == 0) { tvName.setError( frag.getString(R.string.validation_name_should_not_be_blank)); valid = false; } - if (habit.freqNum <= 0) + if (habit.getFreqNum() <= 0) { tvFreqNum.setError( frag.getString(R.string.validation_number_should_be_positive)); valid = false; } - if (habit.freqNum > habit.freqDen) + if (habit.getFreqNum() > habit.getFreqDen()) { tvFreqNum.setError( frag.getString(R.string.validation_at_most_one_rep_per_day)); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java index b63a95f24..777c32f7c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java @@ -19,6 +19,7 @@ package org.isoron.uhabits.ui.habits.edit; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.CreateHabitCommand; @@ -36,9 +37,10 @@ public class CreateHabitDialogFragment extends BaseDialogFragment protected void initializeHabits() { modifiedHabit = new Habit(); - modifiedHabit.freqNum = 1; - modifiedHabit.freqDen = 1; - modifiedHabit.color = prefs.getDefaultHabitColor(modifiedHabit.color); + modifiedHabit.setFreqNum(1); + modifiedHabit.setFreqDen(1); + modifiedHabit.setColor( + prefs.getDefaultHabitColor(modifiedHabit.getColor())); } protected void saveHabit() diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java index 7adf8f3e8..2541100d6 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java @@ -21,13 +21,20 @@ package org.isoron.uhabits.ui.habits.edit; import android.os.Bundle; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.EditHabitCommand; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; + +import javax.inject.Inject; public class EditHabitDialogFragment extends BaseDialogFragment { + @Inject + HabitList habitList; + public static EditHabitDialogFragment newInstance(long habitId) { EditHabitDialogFragment frag = new EditHabitDialogFragment(); @@ -46,14 +53,18 @@ public class EditHabitDialogFragment extends BaseDialogFragment @Override protected void initializeHabits() { + HabitsApplication.getComponent().inject(this); + Long habitId = (Long) getArguments().get("habitId"); if (habitId == null) throw new IllegalArgumentException("habitId must be specified"); - originalHabit = Habit.get(habitId); - modifiedHabit = new Habit(originalHabit); + originalHabit = habitList.getById(habitId); + modifiedHabit = new Habit(); + modifiedHabit.copyFrom(originalHabit); } + @Override protected void saveHabit() { Command command = new EditHabitCommand(originalHabit, modifiedHabit); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java index f1bd5c1f8..bdf08d88d 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java @@ -27,10 +27,14 @@ import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatDialogFragment; import android.util.DisplayMetrics; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.views.HabitHistoryView; +import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView; + +import javax.inject.Inject; public class HistoryEditorDialog extends AppCompatDialogFragment implements DialogInterface.OnClickListener @@ -41,16 +45,20 @@ public class HistoryEditorDialog extends AppCompatDialogFragment HabitHistoryView historyView; + @Inject + HabitList habitList; + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Context context = getActivity(); + HabitsApplication.getComponent().inject(this); historyView = new HabitHistoryView(context, null); if (savedInstanceState != null) { long id = savedInstanceState.getLong("habit", -1); - if (id > 0) this.habit = Habit.get(id); + if (id > 0) this.habit = habitList.getById(id); } int padding = diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/package-info.java new file mode 100644 index 000000000..b5d29a4f4 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides dialogs for editing habits and related classes. + */ +package org.isoron.uhabits.ui.habits.edit; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java index a111df442..9ca8d433e 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsActivity.java @@ -21,25 +21,33 @@ package org.isoron.uhabits.ui.habits.list; import android.os.Bundle; +import org.isoron.uhabits.HabitsApplication; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.ui.BaseActivity; import org.isoron.uhabits.ui.BaseSystem; +import javax.inject.Inject; + /** * Activity that allows the user to see and modify the list of habits. */ public class ListHabitsActivity extends BaseActivity { + @Inject + HabitList habitList; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + HabitsApplication.getComponent().inject(this); + BaseSystem system = new BaseSystem(this); ListHabitsScreen screen = new ListHabitsScreen(this); ListHabitsController controller = - new ListHabitsController(screen, system); + new ListHabitsController(screen, system, habitList); screen.setController(controller); - setScreen(screen); controller.onStartup(); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java index b5c91775e..72c4a867b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java @@ -26,6 +26,7 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.ExportCSVTask; import org.isoron.uhabits.tasks.ExportDBTask; import org.isoron.uhabits.tasks.ImportDataTask; @@ -48,6 +49,9 @@ public class ListHabitsController @NonNull private final BaseSystem system; + @NonNull + private final HabitList habitList; + @Inject Preferences prefs; @@ -55,17 +59,19 @@ public class ListHabitsController CommandRunner commandRunner; public ListHabitsController(@NonNull ListHabitsScreen screen, - @NonNull BaseSystem system) + @NonNull BaseSystem system, + @NonNull HabitList habitList) { this.screen = screen; this.system = system; + this.habitList = habitList; HabitsApplication.getComponent().inject(this); } public void onExportCSV() { ExportCSVTask task = - new ExportCSVTask(Habit.getAll(true), screen.getProgressBar()); + new ExportCSVTask(habitList.getAll(true), screen.getProgressBar()); task.setListener(filename -> { if (filename != null) screen.showSendFileScreen(filename); else screen.showMessage(R.string.could_not_export); @@ -92,7 +98,7 @@ public class ListHabitsController @Override public void onHabitReorder(@NonNull Habit from, @NonNull Habit to) { - Habit.reorder(from, to); + habitList.reorder(from, to); } public void onImportData(File file) @@ -133,7 +139,8 @@ public class ListHabitsController try { system.dumpBugReportToFile(); - } catch (IOException e) + } + catch (IOException e) { // ignored } @@ -146,7 +153,8 @@ public class ListHabitsController String to = "dev@loophabits.org"; String subject = "Bug Report - Loop Habit Tracker"; screen.showSendEmailScreen(log, to, subject); - } catch (IOException e) + } + catch (IOException e) { e.printStackTrace(); screen.showMessage(R.string.bug_report_failed); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java index 760233e37..b617164d2 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java @@ -115,7 +115,7 @@ public class ListHabitsScreen extends BaseScreen public void showColorPicker(Habit habit, OnColorSelectedListener callback) { - int color = ColorUtils.getColor(activity, habit.color); + int color = ColorUtils.getColor(activity, habit.getColor()); ColorPickerDialog picker = ColorPickerDialog.newInstance(R.string.color_picker_default_title, diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java index d0650ac2d..46e9e09ce 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/package-info.java @@ -18,6 +18,6 @@ */ /** - * Contains controllers that are specific for ListHabitsActivity + * Provides controllers that are specific for {@link org.isoron.uhabits.ui.habits.list.ListHabitsActivity}. */ package org.isoron.uhabits.ui.habits.list.controllers; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java index 6aab91a27..306678390 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java @@ -37,9 +37,10 @@ import java.util.List; import javax.inject.Inject; /** - * Provides data that backs a {@link HabitCardListView}. The data if fetched and - * cached by a {@link HabitCardListCache}. This adapter also holds a list of - * items that have been selected. + * Provides data that backs a {@link HabitCardListView}. + *

+ * The data if fetched and cached by a {@link HabitCardListCache}. This adapter + * also holds a list of items that have been selected. */ public class HabitCardListAdapter extends BaseAdapter implements HabitCardListCache.Listener @@ -200,6 +201,12 @@ public class HabitCardListAdapter extends BaseAdapter this.listView = listView; } + public void setShowArchived(boolean showArchived) + { + cache.setIncludeArchived(showArchived); + cache.refreshAllHabits(true); + } + /** * Selects or deselects the item at a given position. * @@ -213,10 +220,4 @@ public class HabitCardListAdapter extends BaseAdapter else selected.remove(h); notifyDataSetChanged(); } - - public void setShowArchived(boolean showArchived) - { - cache.setIncludeArchived(showArchived); - cache.refreshAllHabits(true); - } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java index 0fb5711b1..874b01e46 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java @@ -26,6 +26,7 @@ import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.DateUtils; @@ -63,6 +64,9 @@ public class HabitCardListCache implements CommandRunner.Listener @Inject CommandRunner commandRunner; + @Inject + HabitList allHabits; + public HabitCardListCache() { data = new CacheData(); @@ -148,7 +152,7 @@ public class HabitCardListCache implements CommandRunner.Listener data.habitsList.remove(from); data.habitsList.add(to, fromHabit); - Habit.reorder(fromHabit, toHabit); + allHabits.reorder(fromHabit, toHabit); } public void setCheckmarkCount(int checkmarkCount) @@ -227,7 +231,7 @@ public class HabitCardListCache implements CommandRunner.Listener public void fetchHabits() { - habitsList = Habit.getAll(includeArchived); + habitsList = allHabits.getAll(includeArchived); for (Habit h : habitsList) habits.put(h.getId(), h); } @@ -272,9 +276,9 @@ public class HabitCardListCache implements CommandRunner.Listener if (isCancelled()) return; Long id = h.getId(); - newData.scores.put(id, h.scores.getTodayValue()); + newData.scores.put(id, h.getScores().getTodayValue()); newData.checkmarks.put(id, - h.checkmarks.getValues(dateFrom, dateTo)); + h.getCheckmarks().getValues(dateFrom, dateTo)); publishProgress(current++, newData.habits.size()); } @@ -316,12 +320,12 @@ public class HabitCardListCache implements CommandRunner.Listener long dateFrom = dateTo - (checkmarkCount - 1) * DateUtils.millisecondsInOneDay; - Habit h = Habit.get(id); + Habit h = allHabits.getById(id); if (h == null) return; data.habits.put(id, h); - data.scores.put(id, h.scores.getTodayValue()); - data.checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo)); + data.scores.put(id, h.getScores().getTodayValue()); + data.checkmarks.put(id, h.getCheckmarks().getValues(dateFrom, dateTo)); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java index 06a063daf..06b94b85b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/package-info.java @@ -18,6 +18,6 @@ */ /** - * Contains model classes that are specific for ListHabitsActivity + * Provides models that are specific for {@link org.isoron.uhabits.ui.habits.list.ListHabitsActivity}. */ package org.isoron.uhabits.ui.habits.list.model; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java index 5ae316016..2d3cc559b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/package-info.java @@ -18,6 +18,6 @@ */ /** - * Contains classes for ListHabitsActivity. + * Provides acitivity for listing habits and related classes. */ package org.isoron.uhabits.ui.habits.list; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java index e8e758929..2c12dc181 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java @@ -32,8 +32,8 @@ import android.widget.TextView; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.ui.habits.show.views.RingView; import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.views.RingView; import java.util.Random; @@ -98,7 +98,7 @@ public class HabitCardView extends FrameLayout this.habit = habit; int color = getActiveColor(habit); - label.setText(habit.name); + label.setText(habit.getName()); label.setTextColor(color); scoreRing.setColor(color); checkmarkPanel.setColor(color); @@ -136,7 +136,7 @@ public class HabitCardView extends FrameLayout { int mediumContrastColor = getStyledColor(context, R.attr.mediumContrastTextColor); - int activeColor = ColorUtils.getColor(context, habit.color); + int activeColor = ColorUtils.getColor(context, habit.getColor()); if (habit.isArchived()) activeColor = mediumContrastColor; return activeColor; @@ -173,7 +173,7 @@ public class HabitCardView extends FrameLayout }; Random rand = new Random(); - int color = ColorUtils.CSV_PALETTE[rand.nextInt(10)]; + int color = ColorUtils.getAndroidTestColor(rand.nextInt(10)); int[] values = { rand.nextInt(3), rand.nextInt(3), diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java index 929e801c5..365495876 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java @@ -24,10 +24,14 @@ import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBar; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.ui.BaseActivity; +import javax.inject.Inject; + /** * Activity that allows the user to see more information about a single habit. * Shows all the metadata for the habit, in addition to several charts. @@ -36,13 +40,17 @@ public class ShowHabitActivity extends BaseActivity { private Habit habit; + @Inject + HabitList habitList; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + HabitsApplication.getComponent().inject(this); Uri data = getIntent().getData(); - habit = Habit.get(ContentUris.parseId(data)); + habit = habitList.getById(ContentUris.parseId(data)); setContentView(R.layout.show_habit_activity); // setupSupportActionBar(true); @@ -56,7 +64,7 @@ public class ShowHabitActivity extends BaseActivity ActionBar actionBar = getSupportActionBar(); if (actionBar == null) return; - actionBar.setTitle(habit.name); + actionBar.setTitle(habit.getName()); // setupActionBarColor(ColorUtils.getColor(this, habit.color)); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 3ba99c833..ac2c611fa 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -39,11 +39,11 @@ import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; import org.isoron.uhabits.ui.habits.edit.HistoryEditorDialog; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.views.HabitDataView; -import org.isoron.uhabits.views.HabitFrequencyView; -import org.isoron.uhabits.views.HabitHistoryView; -import org.isoron.uhabits.views.HabitScoreView; -import org.isoron.uhabits.views.HabitStreakView; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.HabitFrequencyView; +import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView; +import org.isoron.uhabits.ui.habits.show.views.HabitScoreView; +import org.isoron.uhabits.ui.habits.show.views.HabitStreakView; import java.util.LinkedList; import java.util.List; @@ -212,13 +212,13 @@ public class ShowHabitFragment extends Fragment public void onStart() { super.onStart(); - habit.observable.addListener(this); + habit.getObservable().addListener(this); } @Override public void onPause() { - habit.observable.removeListener(this); + habit.getObservable().removeListener(this); super.onPause(); } @@ -234,9 +234,9 @@ public class ShowHabitFragment extends Fragment long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; long lastYear = today - 365 * DateUtils.millisecondsInOneDay; - todayScore = (float) habit.scores.getTodayValue(); - lastMonthScore = (float) habit.scores.getValue(lastMonth); - lastYearScore = (float) habit.scores.getValue(lastYear); + todayScore = (float) habit.getScores().getTodayValue(); + lastMonthScore = (float) habit.getScores().getValue(lastMonth); + lastYearScore = (float) habit.getScores().getValue(lastYear); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java index 6a76d4f32..dabc9a3c9 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -25,10 +25,10 @@ import android.widget.TextView; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.ui.habits.show.views.RingView; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.views.RingView; public class ShowHabitHelper { @@ -44,8 +44,8 @@ public class ShowHabitHelper if (fragment.habit == null) return ""; Resources resources = fragment.getResources(); - Integer freqNum = fragment.habit.freqNum; - Integer freqDen = fragment.habit.freqDen; + Integer freqNum = fragment.habit.getFreqNum(); + Integer freqDen = fragment.habit.getFreqDen(); if (freqNum.equals(freqDen)) return resources.getString(R.string.every_day); @@ -76,7 +76,8 @@ public class ShowHabitHelper RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); int androidColor = - ColorUtils.getColor(fragment.getActivity(), fragment.habit.color); + ColorUtils.getColor(fragment.getActivity(), + fragment.habit.getColor()); scoreRing.setColor(androidColor); scoreRing.setPercentage(todayPercentage); @@ -109,13 +110,14 @@ public class ShowHabitHelper TextView questionLabel = (TextView) view.findViewById(R.id.questionLabel); questionLabel.setTextColor(fragment.activeColor); - questionLabel.setText(fragment.habit.description); + questionLabel.setText(fragment.habit.getDescription()); TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel); if (fragment.habit.hasReminder()) reminderLabel.setText( DateUtils.formatTime(fragment.getActivity(), - fragment.habit.reminderHour, fragment.habit.reminderMin)); + fragment.habit.getReminderHour(), + fragment.habit.getReminderMin())); else reminderLabel.setText( fragment.getResources().getString(R.string.reminder_off)); @@ -123,7 +125,7 @@ public class ShowHabitHelper (TextView) view.findViewById(R.id.frequencyLabel); frequencyLabel.setText(getFreqText()); - if (fragment.habit.description.isEmpty()) + if (fragment.habit.getDescription().isEmpty()) questionLabel.setVisibility(View.GONE); } @@ -143,14 +145,15 @@ public class ShowHabitHelper TextView textView = (TextView) view.findViewById(viewId); int androidColor = - ColorUtils.getColor(fragment.activity, fragment.habit.color); + ColorUtils.getColor(fragment.activity, fragment.habit.getColor()); textView.setTextColor(androidColor); } void updateColors() { fragment.activeColor = - ColorUtils.getColor(fragment.getContext(), fragment.habit.color); + ColorUtils.getColor(fragment.getContext(), + fragment.habit.getColor()); fragment.inactiveColor = InterfaceUtils.getStyledColor(fragment.getContext(), R.attr.mediumContrastTextColor); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/package-info.java new file mode 100644 index 000000000..a3146ed7a --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/package-info.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +/** + * Provides activity that display detailed habit information and related + * classes. + */ +package org.isoron.uhabits.ui.habits.show; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java similarity index 95% rename from app/src/main/java/org/isoron/uhabits/views/HabitDataView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java index b1e239d5e..d9163f0e5 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.ui.habits.show.views; import org.isoron.uhabits.models.Habit; diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java index 212aaa570..dd8fbdb22 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.ui.habits.show.views; import android.content.Context; import android.graphics.Canvas; @@ -26,12 +26,12 @@ import android.graphics.RectF; import android.util.AttributeSet; import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.ModelObservable; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.models.Habit; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -101,7 +101,8 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV { if(habit != null) { - this.primaryColor = ColorUtils.getColor(getContext(), habit.color); + this.primaryColor = ColorUtils.getColor(getContext(), + habit.getColor()); } textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); @@ -177,7 +178,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV if(isInEditMode()) generateRandomData(); else if(habit != null) { - frequency = habit.repetitions.getWeekdayFrequency(); + frequency = habit.getRepetitions().getWeekdayFrequency(); createColors(); } @@ -314,15 +315,15 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV refreshData(); } }.execute(); - habit.observable.addListener(this); - habit.checkmarks.observable.addListener(this); + habit.getObservable().addListener(this); + habit.getCheckmarks().observable.addListener(this); } @Override protected void onDetachedFromWindow() { - habit.checkmarks.observable.removeListener(this); - habit.observable.removeListener(this); + habit.getCheckmarks().observable.removeListener(this); + habit.getObservable().removeListener(this); super.onDetachedFromWindow(); } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java similarity index 97% rename from app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java index d390fdb42..b855b8e79 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.ui.habits.show.views; import android.content.Context; import android.graphics.Canvas; @@ -167,7 +167,8 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie private void createColors() { if(habit != null) - this.primaryColor = ColorUtils.getColor(getContext(), habit.color); + this.primaryColor = ColorUtils.getColor(getContext(), + habit.getColor()); if(isBackgroundTransparent) primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f); @@ -216,7 +217,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie else { if(habit == null) return; - checkmarks = habit.checkmarks.getAllValues(); + checkmarks = habit.getCheckmarks().getAllValues(); createColors(); } @@ -424,15 +425,15 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie refreshData(); } }.execute(); - habit.observable.addListener(this); - habit.checkmarks.observable.addListener(this); + habit.getObservable().addListener(this); + habit.getCheckmarks().observable.addListener(this); } @Override protected void onDetachedFromWindow() { - habit.checkmarks.observable.removeListener(this); - habit.observable.removeListener(this); + habit.getCheckmarks().observable.removeListener(this); + habit.getObservable().removeListener(this); super.onDetachedFromWindow(); } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java similarity index 80% rename from app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java index 5a336d101..0b7cf2611 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.ui.habits.show.views; import android.content.Context; import android.graphics.Bitmap; @@ -45,47 +45,69 @@ import java.util.GregorianCalendar; import java.util.Random; public class HabitScoreView extends ScrollableDataView - implements HabitDataView, ModelObservable.Listener + implements HabitDataView, ModelObservable.Listener { public static final PorterDuffXfermode XFERMODE_CLEAR = - new PorterDuffXfermode(PorterDuff.Mode.CLEAR); + new PorterDuffXfermode(PorterDuff.Mode.CLEAR); + public static final PorterDuffXfermode XFERMODE_SRC = - new PorterDuffXfermode(PorterDuff.Mode.SRC); + new PorterDuffXfermode(PorterDuff.Mode.SRC); - public static int DEFAULT_BUCKET_SIZES[] = { 1, 7, 31, 92, 365 }; + public static int DEFAULT_BUCKET_SIZES[] = {1, 7, 31, 92, 365}; private Paint pGrid; + private float em; + private Habit habit; private SimpleDateFormat dfMonth; + private SimpleDateFormat dfDay; + private SimpleDateFormat dfYear; private Paint pText, pGraph; + private RectF rect, prevRect; + private int baseSize; + private int paddingTop; private float columnWidth; + private int columnHeight; + private int nColumns; private int textColor; + private int gridColor; @Nullable private int[] scores; private int primaryColor; + private int bucketSize = 7; + private int footerHeight; + private int backgroundColor; private Bitmap drawingCache; + private Canvas cacheCanvas; + private boolean isTransparencyEnabled; + private int skipYear = 0; + + private String previousYearText; + + private String previousMonthText; + public HabitScoreView(Context context) { super(context); @@ -99,105 +121,20 @@ public class HabitScoreView extends ScrollableDataView init(); } - public void setHabit(Habit habit) - { - this.habit = habit; - createColors(); - } - - private void init() - { - createPaints(); - createColors(); - - dfYear = DateUtils.getDateFormat("yyyy"); - dfMonth = DateUtils.getDateFormat("MMM"); - dfDay = DateUtils.getDateFormat("d"); - - rect = new RectF(); - prevRect = new RectF(); - } - - private void createColors() - { - if(habit != null) - this.primaryColor = ColorUtils.getColor(getContext(), habit.color); - - textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - gridColor = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor); - backgroundColor = InterfaceUtils.getStyledColor(getContext(), R.attr.cardBackgroundColor); - } - - protected void createPaints() - { - pText = new Paint(); - pText.setAntiAlias(true); - - pGraph = new Paint(); - pGraph.setTextAlign(Paint.Align.CENTER); - pGraph.setAntiAlias(true); - - pGrid = new Paint(); - pGrid.setAntiAlias(true); - } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + public void onModelChange() { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(width, height); + refreshData(); } @Override - protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) - { - if(height < 9) height = 200; - - float maxTextSize = getResources().getDimension(R.dimen.tinyTextSize); - float textSize = height * 0.06f; - pText.setTextSize(Math.min(textSize, maxTextSize)); - em = pText.getFontSpacing(); - - footerHeight = (int)(3 * em); - paddingTop = (int) (em); - - baseSize = (height - footerHeight - paddingTop) / 8; - setScrollerBucketSize(baseSize); - - columnWidth = baseSize; - columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f); - columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f); - - nColumns = (int) (width / columnWidth); - columnWidth = (float) width / nColumns; - - columnHeight = 8 * baseSize; - - float minStrokeWidth = InterfaceUtils.dpToPixels(getContext(), 1); - pGraph.setTextSize(baseSize * 0.5f); - pGraph.setStrokeWidth(baseSize * 0.1f); - pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f)); - - if(isTransparencyEnabled) - initCache(width, height); - } - - private void initCache(int width, int height) - { - if (drawingCache != null) drawingCache.recycle(); - drawingCache = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - cacheCanvas = new Canvas(drawingCache); - } - public void refreshData() { - if(isInEditMode()) - generateRandomData(); + if (isInEditMode()) generateRandomData(); else { if (habit == null) return; - scores = habit.scores.getAllValues(bucketSize); + scores = habit.getScores().getAllValues(bucketSize); createColors(); } @@ -209,96 +146,46 @@ public class HabitScoreView extends ScrollableDataView this.bucketSize = bucketSize; } - private void generateRandomData() + @Override + public void setHabit(Habit habit) { - Random random = new Random(); - scores = new int[100]; - scores[0] = Score.MAX_VALUE / 2; - - for(int i = 1; i < 100; i++) - { - int step = Score.MAX_VALUE / 10; - scores[i] = scores[i - 1] + random.nextInt(step * 2) - step; - scores[i] = Math.max(0, Math.min(Score.MAX_VALUE, scores[i])); - } + this.habit = habit; + createColors(); } - @Override - protected void onDraw(Canvas canvas) + public void setIsTransparencyEnabled(boolean enabled) { - super.onDraw(canvas); - Canvas activeCanvas; - - if(isTransparencyEnabled) - { - if(drawingCache == null) initCache(getWidth(), getHeight()); - - activeCanvas = cacheCanvas; - drawingCache.eraseColor(Color.TRANSPARENT); - } - else - { - activeCanvas = canvas; - } - - if (habit == null || scores == null) return; - - rect.set(0, 0, nColumns * columnWidth, columnHeight); - rect.offset(0, paddingTop); - - drawGrid(activeCanvas, rect); - - pText.setColor(textColor); - pGraph.setColor(primaryColor); - prevRect.setEmpty(); - - previousMonthText = ""; - previousYearText = ""; - skipYear = 0; - - long currentDate = DateUtils.getStartOfToday(); - - for(int k = 0; k < nColumns + getDataOffset() - 1; k++) - currentDate -= bucketSize * DateUtils.millisecondsInOneDay; - - for (int k = 0; k < nColumns; k++) - { - int score = 0; - int offset = nColumns - k - 1 + getDataOffset(); - if(offset < scores.length) score = scores[offset]; - - double relativeScore = ((double) score) / Score.MAX_VALUE; - int height = (int) (columnHeight * relativeScore); - - rect.set(0, 0, baseSize, baseSize); - rect.offset(k * columnWidth + (columnWidth - baseSize) / 2, - paddingTop + columnHeight - height - baseSize / 2); - - if (!prevRect.isEmpty()) - { - drawLine(activeCanvas, prevRect, rect); - drawMarker(activeCanvas, prevRect); - } - - if (k == nColumns - 1) drawMarker(activeCanvas, rect); + this.isTransparencyEnabled = enabled; + createColors(); + requestLayout(); + } - prevRect.set(rect); - rect.set(0, 0, columnWidth, columnHeight); - rect.offset(k * columnWidth, paddingTop); + private void createColors() + { + if (habit != null) this.primaryColor = + ColorUtils.getColor(getContext(), habit.getColor()); + + textColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + gridColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + backgroundColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.cardBackgroundColor); + } - drawFooter(activeCanvas, rect, currentDate); + protected void createPaints() + { + pText = new Paint(); + pText.setAntiAlias(true); - currentDate += bucketSize * DateUtils.millisecondsInOneDay; - } + pGraph = new Paint(); + pGraph.setTextAlign(Paint.Align.CENTER); + pGraph.setAntiAlias(true); - if(activeCanvas != canvas) - canvas.drawBitmap(drawingCache, 0, 0, null); + pGrid = new Paint(); + pGrid.setAntiAlias(true); } - private int skipYear = 0; - private String previousYearText; - private String previousMonthText; - private void drawFooter(Canvas canvas, RectF rect, long currentDate) { String yearText = dfYear.format(currentDate); @@ -311,29 +198,30 @@ public class HabitScoreView extends ScrollableDataView int year = calendar.get(Calendar.YEAR); boolean shouldPrintYear = true; - if(yearText.equals(previousYearText)) shouldPrintYear = false; - if(bucketSize >= 365 && (year % 2) != 0) shouldPrintYear = false; + if (yearText.equals(previousYearText)) shouldPrintYear = false; + if (bucketSize >= 365 && (year % 2) != 0) shouldPrintYear = false; - if(skipYear > 0) + if (skipYear > 0) { skipYear--; shouldPrintYear = false; } - if(shouldPrintYear) + if (shouldPrintYear) { previousYearText = yearText; previousMonthText = ""; pText.setTextAlign(Paint.Align.CENTER); - canvas.drawText(yearText, rect.centerX(), rect.bottom + em * 2.2f, pText); + canvas.drawText(yearText, rect.centerX(), rect.bottom + em * 2.2f, + pText); skipYear = 1; } - if(bucketSize < 365) + if (bucketSize < 365) { - if(!monthText.equals(previousMonthText)) + if (!monthText.equals(previousMonthText)) { previousMonthText = monthText; text = monthText; @@ -344,11 +232,11 @@ public class HabitScoreView extends ScrollableDataView } pText.setTextAlign(Paint.Align.CENTER); - canvas.drawText(text, rect.centerX(), rect.bottom + em * 1.2f, pText); + canvas.drawText(text, rect.centerX(), rect.bottom + em * 1.2f, + pText); } } - private void drawGrid(Canvas canvas, RectF rGrid) { int nRows = 5; @@ -360,9 +248,10 @@ public class HabitScoreView extends ScrollableDataView for (int i = 0; i < nRows; i++) { - canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)), rGrid.left + 0.5f * em, - rGrid.top + 1f * em, pText); - canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid); + canvas.drawText(String.format("%d%%", (100 - i * 100 / nRows)), + rGrid.left + 0.5f * em, rGrid.top + 1f * em, pText); + canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, + pGrid); rGrid.offset(0, rowHeight); } @@ -372,8 +261,8 @@ public class HabitScoreView extends ScrollableDataView private void drawLine(Canvas canvas, RectF rectFrom, RectF rectTo) { pGraph.setColor(primaryColor); - canvas.drawLine(rectFrom.centerX(), rectFrom.centerY(), rectTo.centerX(), rectTo.centerY(), - pGraph); + canvas.drawLine(rectFrom.centerX(), rectFrom.centerY(), + rectTo.centerX(), rectTo.centerY(), pGraph); } private void drawMarker(Canvas canvas, RectF rect) @@ -390,23 +279,36 @@ public class HabitScoreView extends ScrollableDataView setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor); canvas.drawOval(rect, pGraph); - if(isTransparencyEnabled) - pGraph.setXfermode(XFERMODE_SRC); + if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC); } - public void setIsTransparencyEnabled(boolean enabled) + private void generateRandomData() { - this.isTransparencyEnabled = enabled; - createColors(); - requestLayout(); + Random random = new Random(); + scores = new int[100]; + scores[0] = Score.MAX_VALUE / 2; + + for (int i = 1; i < 100; i++) + { + int step = Score.MAX_VALUE / 10; + scores[i] = scores[i - 1] + random.nextInt(step * 2) - step; + scores[i] = Math.max(0, Math.min(Score.MAX_VALUE, scores[i])); + } } - private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color) + private float getMaxDayWidth() { - if(isTransparencyEnabled) - p.setXfermode(mode); - else - p.setColor(color); + float maxDayWidth = 0; + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); + + for (int i = 0; i < 28; i++) + { + day.set(Calendar.DAY_OF_MONTH, i); + float monthWidth = pText.measureText(dfMonth.format(day.getTime())); + maxDayWidth = Math.max(maxDayWidth, monthWidth); + } + + return maxDayWidth; } private float getMaxMonthWidth() @@ -414,7 +316,7 @@ public class HabitScoreView extends ScrollableDataView float maxMonthWidth = 0; GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); - for(int i = 0; i < 12; i++) + for (int i = 0; i < 12; i++) { day.set(Calendar.MONTH, i); float monthWidth = pText.measureText(dfMonth.format(day.getTime())); @@ -424,19 +326,25 @@ public class HabitScoreView extends ScrollableDataView return maxMonthWidth; } - private float getMaxDayWidth() + private void init() { - float maxDayWidth = 0; - GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); + createPaints(); + createColors(); - for(int i = 0; i < 28; i++) - { - day.set(Calendar.DAY_OF_MONTH, i); - float monthWidth = pText.measureText(dfMonth.format(day.getTime())); - maxDayWidth = Math.max(maxDayWidth, monthWidth); - } + dfYear = DateUtils.getDateFormat("yyyy"); + dfMonth = DateUtils.getDateFormat("MMM"); + dfDay = DateUtils.getDateFormat("d"); - return maxDayWidth; + rect = new RectF(); + prevRect = new RectF(); + } + + private void initCache(int width, int height) + { + if (drawingCache != null) drawingCache.recycle(); + drawingCache = + Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + cacheCanvas = new Canvas(drawingCache); } @Override @@ -451,21 +359,136 @@ public class HabitScoreView extends ScrollableDataView refreshData(); } }.execute(); - habit.observable.addListener(this); - habit.scores.observable.addListener(this); + habit.getObservable().addListener(this); + habit.getScores().getObservable().addListener(this); } @Override protected void onDetachedFromWindow() { - habit.scores.observable.removeListener(this); - habit.observable.removeListener(this); + habit.getScores().getObservable().removeListener(this); + habit.getObservable().removeListener(this); super.onDetachedFromWindow(); } @Override - public void onModelChange() + protected void onDraw(Canvas canvas) { - refreshData(); + super.onDraw(canvas); + Canvas activeCanvas; + + if (isTransparencyEnabled) + { + if (drawingCache == null) initCache(getWidth(), getHeight()); + + activeCanvas = cacheCanvas; + drawingCache.eraseColor(Color.TRANSPARENT); + } + else + { + activeCanvas = canvas; + } + + if (habit == null || scores == null) return; + + rect.set(0, 0, nColumns * columnWidth, columnHeight); + rect.offset(0, paddingTop); + + drawGrid(activeCanvas, rect); + + pText.setColor(textColor); + pGraph.setColor(primaryColor); + prevRect.setEmpty(); + + previousMonthText = ""; + previousYearText = ""; + skipYear = 0; + + long currentDate = DateUtils.getStartOfToday(); + + for (int k = 0; k < nColumns + getDataOffset() - 1; k++) + currentDate -= bucketSize * DateUtils.millisecondsInOneDay; + + for (int k = 0; k < nColumns; k++) + { + int score = 0; + int offset = nColumns - k - 1 + getDataOffset(); + if (offset < scores.length) score = scores[offset]; + + double relativeScore = ((double) score) / Score.MAX_VALUE; + int height = (int) (columnHeight * relativeScore); + + rect.set(0, 0, baseSize, baseSize); + rect.offset(k * columnWidth + (columnWidth - baseSize) / 2, + paddingTop + columnHeight - height - baseSize / 2); + + if (!prevRect.isEmpty()) + { + drawLine(activeCanvas, prevRect, rect); + drawMarker(activeCanvas, prevRect); + } + + if (k == nColumns - 1) drawMarker(activeCanvas, rect); + + prevRect.set(rect); + rect.set(0, 0, columnWidth, columnHeight); + rect.offset(k * columnWidth, paddingTop); + + drawFooter(activeCanvas, rect, currentDate); + + currentDate += bucketSize * DateUtils.millisecondsInOneDay; + } + + if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int width, + int height, + int oldWidth, + int oldHeight) + { + if (height < 9) height = 200; + + float maxTextSize = getResources().getDimension(R.dimen.tinyTextSize); + float textSize = height * 0.06f; + pText.setTextSize(Math.min(textSize, maxTextSize)); + em = pText.getFontSpacing(); + + footerHeight = (int) (3 * em); + paddingTop = (int) (em); + + baseSize = (height - footerHeight - paddingTop) / 8; + setScrollerBucketSize(baseSize); + + columnWidth = baseSize; + columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f); + columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f); + + nColumns = (int) (width / columnWidth); + columnWidth = (float) width / nColumns; + + columnHeight = 8 * baseSize; + + float minStrokeWidth = InterfaceUtils.dpToPixels(getContext(), 1); + pGraph.setTextSize(baseSize * 0.5f); + pGraph.setStrokeWidth(baseSize * 0.1f); + pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f)); + + if (isTransparencyEnabled) initCache(width, height); + } + + private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color) + { + if (isTransparencyEnabled) p.setXfermode(mode); + else p.setColor(color); } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java similarity index 73% rename from app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java index 6019ef4a1..59d2bc78a 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.ui.habits.show.views; import android.content.Context; import android.graphics.Canvas; @@ -28,12 +28,12 @@ import android.util.AttributeSet; import android.view.View; import org.isoron.uhabits.R; +import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.ModelObservable; +import org.isoron.uhabits.models.Streak; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Streak; import java.text.DateFormat; import java.util.Collections; @@ -41,29 +41,45 @@ import java.util.Date; import java.util.List; import java.util.TimeZone; -public class HabitStreakView extends View implements HabitDataView, ModelObservable.Listener +public class HabitStreakView extends View + implements HabitDataView, ModelObservable.Listener { private Habit habit; + private Paint paint; private long minLength; + private long maxLength; private int[] colors; + private RectF rect; + private int baseSize; + private int primaryColor; + private List streaks; private boolean isBackgroundTransparent; + private DateFormat dateFormat; + private int width; + private float em; + private float maxLabelWidth; + private float textMargin; + private boolean shouldShowLabels; + private int maxStreakCount; + private int textColor; + private int reverseTextColor; public HabitStreakView(Context context) @@ -79,55 +95,39 @@ public class HabitStreakView extends View implements HabitDataView, ModelObserva init(); } - public void setHabit(Habit habit) + @Override + public void onModelChange() { - this.habit = habit; - createColors(); + refreshData(); } - private void init() + @Override + public void refreshData() { - createPaints(); + if (habit == null) return; + streaks = habit.getStreaks().getBest(maxStreakCount); createColors(); - - streaks = Collections.emptyList(); - - dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM); - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - rect = new RectF(); - maxStreakCount = 10; - baseSize = getResources().getDimensionPixelSize(R.dimen.baseSize); + updateMaxMin(); + postInvalidate(); } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + public void setHabit(Habit habit) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(width, height); + this.habit = habit; + createColors(); } - @Override - protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) + public void setIsBackgroundTransparent(boolean isBackgroundTransparent) { - maxStreakCount = height / baseSize; - this.width = width; - - float minTextSize = getResources().getDimension(R.dimen.tinyTextSize); - float maxTextSize = getResources().getDimension(R.dimen.regularTextSize); - float textSize = baseSize * 0.5f; - - paint.setTextSize(Math.max(Math.min(textSize, maxTextSize), minTextSize)); - em = paint.getFontSpacing(); - textMargin = 0.5f * em; - - updateMaxMin(); + this.isBackgroundTransparent = isBackgroundTransparent; + createColors(); } private void createColors() { - if(habit != null) - this.primaryColor = ColorUtils.getColor(getContext(), habit.color); + if (habit != null) this.primaryColor = + ColorUtils.getColor(getContext(), habit.getColor()); int red = Color.red(primaryColor); int green = Color.green(primaryColor); @@ -137,9 +137,12 @@ public class HabitStreakView extends View implements HabitDataView, ModelObserva colors[3] = primaryColor; colors[2] = Color.argb(192, red, green, blue); colors[1] = Color.argb(96, red, green, blue); - colors[0] = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor); - textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - reverseTextColor = InterfaceUtils.getStyledColor(getContext(), R.attr.highContrastReverseTextColor); + colors[0] = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + textColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + reverseTextColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.highContrastReverseTextColor); } protected void createPaints() @@ -149,63 +152,17 @@ public class HabitStreakView extends View implements HabitDataView, ModelObserva paint.setAntiAlias(true); } - public void refreshData() - { - if(habit == null) return; - streaks = habit.streaks.getAll(maxStreakCount); - createColors(); - updateMaxMin(); - postInvalidate(); - } - - @Override - protected void onDraw(Canvas canvas) - { - super.onDraw(canvas); - if(streaks.size() == 0) return; - - rect.set(0, 0, width, baseSize); - - for(Streak s : streaks) - { - drawRow(canvas, s, rect); - rect.offset(0, baseSize); - } - } - - private void updateMaxMin() - { - maxLength = 0; - minLength = Long.MAX_VALUE; - shouldShowLabels = true; - - for (Streak s : streaks) - { - maxLength = Math.max(maxLength, s.length); - minLength = Math.min(minLength, s.length); - - float lw1 = paint.measureText(dateFormat.format(new Date(s.start))); - float lw2 = paint.measureText(dateFormat.format(new Date(s.end))); - maxLabelWidth = Math.max(maxLabelWidth, Math.max(lw1, lw2)); - } - - if(width - 2 * maxLabelWidth < width * 0.25f) - { - maxLabelWidth = 0; - shouldShowLabels = false; - } - } - private void drawRow(Canvas canvas, Streak streak, RectF rect) { - if(maxLength == 0) return; + if (maxLength == 0) return; - float percentage = (float) streak.length / maxLength; + float percentage = (float) streak.getLength() / maxLength; float availableWidth = width - 2 * maxLabelWidth; - if(shouldShowLabels) availableWidth -= 2 * textMargin; + if (shouldShowLabels) availableWidth -= 2 * textMargin; float barWidth = percentage * availableWidth; - float minBarWidth = paint.measureText(streak.length.toString()) + em; + float minBarWidth = + paint.measureText(Long.toString(streak.getLength())) + em; barWidth = Math.max(barWidth, minBarWidth); float gap = (width - barWidth) / 2; @@ -213,19 +170,20 @@ public class HabitStreakView extends View implements HabitDataView, ModelObserva paint.setColor(percentageToColor(percentage)); - canvas.drawRect(rect.left + gap, rect.top + paddingTopBottom, rect.right - gap, - rect.bottom - paddingTopBottom, paint); + canvas.drawRect(rect.left + gap, rect.top + paddingTopBottom, + rect.right - gap, rect.bottom - paddingTopBottom, paint); float yOffset = rect.centerY() + 0.3f * em; paint.setColor(reverseTextColor); paint.setTextAlign(Paint.Align.CENTER); - canvas.drawText(streak.length.toString(), rect.centerX(), yOffset, paint); + canvas.drawText(Long.toString(streak.getLength()), rect.centerX(), + yOffset, paint); - if(shouldShowLabels) + if (shouldShowLabels) { - String startLabel = dateFormat.format(new Date(streak.start)); - String endLabel = dateFormat.format(new Date(streak.end)); + String startLabel = dateFormat.format(new Date(streak.getStart())); + String endLabel = dateFormat.format(new Date(streak.getEnd())); paint.setColor(textColor); paint.setTextAlign(Paint.Align.RIGHT); @@ -236,20 +194,19 @@ public class HabitStreakView extends View implements HabitDataView, ModelObserva } } - private int percentageToColor(float percentage) - { - if(percentage >= 1.0f) return colors[3]; - if(percentage >= 0.8f) return colors[2]; - if(percentage >= 0.5f) return colors[1]; - return colors[0]; - } - - public void setIsBackgroundTransparent(boolean isBackgroundTransparent) + private void init() { - this.isBackgroundTransparent = isBackgroundTransparent; + createPaints(); createColors(); - } + streaks = Collections.emptyList(); + + dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + rect = new RectF(); + maxStreakCount = 10; + baseSize = getResources().getDimensionPixelSize(R.dimen.baseSize); + } @Override protected void onAttachedToWindow() @@ -263,21 +220,93 @@ public class HabitStreakView extends View implements HabitDataView, ModelObserva refreshData(); } }.execute(); - habit.observable.addListener(this); - habit.streaks.observable.addListener(this); + habit.getObservable().addListener(this); + habit.getStreaks().getObservable().addListener(this); } @Override protected void onDetachedFromWindow() { - habit.streaks.observable.removeListener(this); - habit.observable.removeListener(this); + habit.getStreaks().getObservable().removeListener(this); + habit.getObservable().removeListener(this); super.onDetachedFromWindow(); } @Override - public void onModelChange() + protected void onDraw(Canvas canvas) { - refreshData(); + super.onDraw(canvas); + if (streaks.size() == 0) return; + + rect.set(0, 0, width, baseSize); + + for (Streak s : streaks) + { + drawRow(canvas, s, rect); + rect.offset(0, baseSize); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int width, + int height, + int oldWidth, + int oldHeight) + { + maxStreakCount = height / baseSize; + this.width = width; + + float minTextSize = getResources().getDimension(R.dimen.tinyTextSize); + float maxTextSize = + getResources().getDimension(R.dimen.regularTextSize); + float textSize = baseSize * 0.5f; + + paint.setTextSize( + Math.max(Math.min(textSize, maxTextSize), minTextSize)); + em = paint.getFontSpacing(); + textMargin = 0.5f * em; + + updateMaxMin(); + } + + private int percentageToColor(float percentage) + { + if (percentage >= 1.0f) return colors[3]; + if (percentage >= 0.8f) return colors[2]; + if (percentage >= 0.5f) return colors[1]; + return colors[0]; + } + + private void updateMaxMin() + { + maxLength = 0; + minLength = Long.MAX_VALUE; + shouldShowLabels = true; + + for (Streak s : streaks) + { + maxLength = Math.max(maxLength, s.getLength()); + minLength = Math.min(minLength, s.getLength()); + + float lw1 = + paint.measureText(dateFormat.format(new Date(s.getStart()))); + float lw2 = + paint.measureText(dateFormat.format(new Date(s.getEnd()))); + maxLabelWidth = Math.max(maxLabelWidth, Math.max(lw1, lw2)); + } + + if (width - 2 * maxLabelWidth < width * 0.25f) + { + maxLabelWidth = 0; + shouldShowLabels = false; + } } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitWidgetView.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitWidgetView.java index b7dc4a918..6b161f016 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitWidgetView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.ui.habits.show.views; import android.content.Context; import android.graphics.Color; @@ -32,8 +32,8 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.InterfaceUtils; import java.util.Arrays; diff --git a/app/src/main/java/org/isoron/uhabits/views/RingView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/RingView.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/views/RingView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/RingView.java index f521fc47d..f471dfce9 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/RingView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.ui.habits.show.views; import android.content.Context; import android.graphics.Bitmap; @@ -70,7 +70,7 @@ public class RingView extends View percentage = 0.0f; precision = 0.01f; - color = ColorUtils.CSV_PALETTE[0]; + color = ColorUtils.getAndroidTestColor(0); thickness = InterfaceUtils.dpToPixels(getContext(), 2); text = ""; textSize = context.getResources().getDimension(R.dimen.smallTextSize); diff --git a/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java similarity index 83% rename from app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java index fbae274b7..746d06cd0 100644 --- a/app/src/main/java/org/isoron/uhabits/views/ScrollableDataView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.ui.habits.show.views; import android.animation.ValueAnimator; import android.content.Context; @@ -28,15 +28,19 @@ import android.view.View; import android.view.ViewParent; import android.widget.Scroller; -public abstract class ScrollableDataView extends View implements GestureDetector.OnGestureListener, - ValueAnimator.AnimatorUpdateListener +public abstract class ScrollableDataView extends View + implements GestureDetector.OnGestureListener, + ValueAnimator.AnimatorUpdateListener { private int dataOffset; + private int scrollerBucketSize; private GestureDetector detector; + private Scroller scroller; + private ValueAnimator scrollAnimator; public ScrollableDataView(Context context) @@ -51,18 +55,24 @@ public abstract class ScrollableDataView extends View implements GestureDetector init(context); } - private void init(Context context) + public int getDataOffset() { - detector = new GestureDetector(context, this); - scroller = new Scroller(context, null, true); - scrollAnimator = ValueAnimator.ofFloat(0, 1); - scrollAnimator.addUpdateListener(this); + return dataOffset; } @Override - public boolean onTouchEvent(MotionEvent event) + public void onAnimationUpdate(ValueAnimator animation) { - return detector.onTouchEvent(event); + if (!scroller.isFinished()) + { + scroller.computeScrollOffset(); + dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize); + postInvalidate(); + } + else + { + scrollAnimator.cancel(); + } } @Override @@ -72,30 +82,40 @@ public abstract class ScrollableDataView extends View implements GestureDetector } @Override - public void onShowPress(MotionEvent e) + public boolean onFling(MotionEvent e1, + MotionEvent e2, + float velocityX, + float velocityY) { + scroller.fling(scroller.getCurrX(), scroller.getCurrY(), + (int) velocityX / 2, 0, 0, 100000, 0, 0); + invalidate(); + scrollAnimator.setDuration(scroller.getDuration()); + scrollAnimator.start(); + + return false; } @Override - public boolean onSingleTapUp(MotionEvent e) + public void onLongPress(MotionEvent e) { - return false; + } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) { - if(scrollerBucketSize == 0) - return false; + if (scrollerBucketSize == 0) return false; - if(Math.abs(dx) > Math.abs(dy)) + if (Math.abs(dx) > Math.abs(dy)) { ViewParent parent = getParent(); - if(parent != null) parent.requestDisallowInterceptTouchEvent(true); + if (parent != null) parent.requestDisallowInterceptTouchEvent(true); } - scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0); + scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), + (int) -dx, (int) dy, 0); scroller.computeScrollOffset(); dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize); postInvalidate(); @@ -104,46 +124,33 @@ public abstract class ScrollableDataView extends View implements GestureDetector } @Override - public void onLongPress(MotionEvent e) + public void onShowPress(MotionEvent e) { } @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) + public boolean onSingleTapUp(MotionEvent e) { - scroller.fling(scroller.getCurrX(), scroller.getCurrY(), (int) velocityX / 2, 0, 0, 100000, - 0, 0); - invalidate(); - - scrollAnimator.setDuration(scroller.getDuration()); - scrollAnimator.start(); - return false; } @Override - public void onAnimationUpdate(ValueAnimator animation) + public boolean onTouchEvent(MotionEvent event) { - if (!scroller.isFinished()) - { - scroller.computeScrollOffset(); - dataOffset = Math.max(0, scroller.getCurrX() / scrollerBucketSize); - postInvalidate(); - } - else - { - scrollAnimator.cancel(); - } + return detector.onTouchEvent(event); } - public int getDataOffset() + public void setScrollerBucketSize(int scrollerBucketSize) { - return dataOffset; + this.scrollerBucketSize = scrollerBucketSize; } - public void setScrollerBucketSize(int scrollerBucketSize) + private void init(Context context) { - this.scrollerBucketSize = scrollerBucketSize; + detector = new GestureDetector(context, this); + scroller = new Scroller(context, null, true); + scrollAnimator = ValueAnimator.ofFloat(0, 1); + scrollAnimator.addUpdateListener(this); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/package-info.java new file mode 100644 index 000000000..5442e7bcf --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/package-info.java @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +/** + * Provides custom views that are used primarily on {@link + * org.isoron.uhabits.ui.habits.show.ShowHabitActivity}. + */ +package org.isoron.uhabits.ui.habits.show.views; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java index 68eee2b1e..200ce960c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java +++ b/app/src/main/java/org/isoron/uhabits/ui/intro/package-info.java @@ -18,6 +18,6 @@ */ /** - * Contains classes for the IntroActivity. + * Provides activity that introduces app to the user and related classes. */ package org.isoron.uhabits.ui.intro; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/package-info.java new file mode 100644 index 000000000..6eb8588d5 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides classes for the Android user interface. + */ +package org.isoron.uhabits.ui; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java b/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java index d7254b864..28a65b5ea 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/package-info.java @@ -18,6 +18,6 @@ */ /** - * Contains classes for the SettingsActivity. + * Provides activity for changing the settings. */ package org.isoron.uhabits.ui.settings; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java b/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java index 1e4656540..e8116c1b0 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java @@ -27,55 +27,79 @@ import org.isoron.uhabits.R; public abstract class ColorUtils { - public static int CSV_PALETTE[] = - { - Color.parseColor("#D32F2F"), // 0 red - Color.parseColor("#E64A19"), // 1 orange - Color.parseColor("#F9A825"), // 2 yellow - Color.parseColor("#AFB42B"), // 3 light green - Color.parseColor("#388E3C"), // 4 dark green - Color.parseColor("#00897B"), // 5 teal - Color.parseColor("#00ACC1"), // 6 cyan - Color.parseColor("#039BE5"), // 7 blue - Color.parseColor("#5E35B1"), // 8 deep purple - Color.parseColor("#8E24AA"), // 9 purple - Color.parseColor("#D81B60"), // 10 pink - Color.parseColor("#303030"), // 11 dark grey - Color.parseColor("#aaaaaa") // 12 light grey + public static String CSV_PALETTE[] = { + "#D32F2F", // 0 red + "#E64A19", // 1 orange + "#F9A825", // 2 yellow + "#AFB42B", // 3 light green + "#388E3C", // 4 dark green + "#00897B", // 5 teal + "#00ACC1", // 6 cyan + "#039BE5", // 7 blue + "#5E35B1", // 8 deep purple + "#8E24AA", // 9 purple + "#D81B60", // 10 pink + "#303030", // 11 dark grey + "#aaaaaa" // 12 light grey }; public static int colorToPaletteIndex(Context context, int color) { int[] palette = getPalette(context); - for(int k = 0; k < palette.length; k++) - if(palette[k] == color) return k; + for (int k = 0; k < palette.length; k++) + if (palette[k] == color) return k; return -1; } - public static int[] getPalette(Context context) + public static int getAndroidTestColor(int index) { - int resourceId = InterfaceUtils.getStyleResource(context, R.attr.palette); - if(resourceId < 0) return CSV_PALETTE; - - return context.getResources().getIntArray(resourceId); + int palette[] = { + Color.parseColor("#D32F2F"), // 0 red + Color.parseColor("#E64A19"), // 1 orange + Color.parseColor("#F9A825"), // 2 yellow + Color.parseColor("#AFB42B"), // 3 light green + Color.parseColor("#388E3C"), // 4 dark green + Color.parseColor("#00897B"), // 5 teal + Color.parseColor("#00ACC1"), // 6 cyan + Color.parseColor("#039BE5"), // 7 blue + Color.parseColor("#5E35B1"), // 8 deep purple + Color.parseColor("#8E24AA"), // 9 purple + Color.parseColor("#D81B60"), // 10 pink + Color.parseColor("#303030"), // 11 dark grey + Color.parseColor("#aaaaaa") // 12 light grey + }; + + return palette[index]; } public static int getColor(Context context, int paletteColor) { - if(context == null) throw new IllegalArgumentException("Context is null"); + if (context == null) + throw new IllegalArgumentException("Context is null"); int palette[] = getPalette(context); - if(paletteColor < 0 || paletteColor >= palette.length) + if (paletteColor < 0 || paletteColor >= palette.length) { - Log.w("ColorHelper", String.format("Invalid color: %d. Returning default.", paletteColor)); + Log.w("ColorHelper", + String.format("Invalid color: %d. Returning default.", + paletteColor)); paletteColor = 0; } return palette[paletteColor]; } + public static int[] getPalette(Context context) + { + int resourceId = + InterfaceUtils.getStyleResource(context, R.attr.palette); + if (resourceId < 0) throw new RuntimeException("resource not found"); + + return context.getResources().getIntArray(resourceId); + } + public static int mixColors(int color1, int color2, float amount) { final byte ALPHA_CHANNEL = 24; @@ -86,36 +110,26 @@ public abstract class ColorUtils final float inverseAmount = 1.0f - amount; int a = ((int) (((float) (color1 >> ALPHA_CHANNEL & 0xff) * amount) + - ((float) (color2 >> ALPHA_CHANNEL & 0xff) * inverseAmount))) & 0xff; + ((float) (color2 >> ALPHA_CHANNEL & 0xff) * + inverseAmount))) & 0xff; int r = ((int) (((float) (color1 >> RED_CHANNEL & 0xff) * amount) + - ((float) (color2 >> RED_CHANNEL & 0xff) * inverseAmount))) & 0xff; + ((float) (color2 >> RED_CHANNEL & 0xff) * + inverseAmount))) & 0xff; int g = ((int) (((float) (color1 >> GREEN_CHANNEL & 0xff) * amount) + - ((float) (color2 >> GREEN_CHANNEL & 0xff) * inverseAmount))) & 0xff; + ((float) (color2 >> GREEN_CHANNEL & 0xff) * + inverseAmount))) & 0xff; int b = ((int) (((float) (color1 & 0xff) * amount) + - ((float) (color2 & 0xff) * inverseAmount))) & 0xff; + ((float) (color2 & 0xff) * inverseAmount))) & 0xff; - return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | b << BLUE_CHANNEL; - } - - public static int setHue(int color, float newHue) - { - return setHSVParameter(color, newHue, 0); - } - - public static int setSaturation(int color, float newSaturation) - { - return setHSVParameter(color, newSaturation, 1); - } - - public static int setValue(int color, float newValue) - { - return setHSVParameter(color, newValue, 2); + return a << ALPHA_CHANNEL | r << RED_CHANNEL | g << GREEN_CHANNEL | + b << BLUE_CHANNEL; } public static int setAlpha(int color, float newAlpha) { int intAlpha = (int) (newAlpha * 255); - return Color.argb(intAlpha, Color.red(color), Color.green(color), Color.blue(color)); + return Color.argb(intAlpha, Color.red(color), Color.green(color), + Color.blue(color)); } public static int setMinValue(int color, float newValue) @@ -126,16 +140,4 @@ public abstract class ColorUtils return Color.HSVToColor(hsv); } - private static int setHSVParameter(int color, float newValue, int index) - { - float hsv[] = new float[3]; - Color.colorToHSV(color, hsv); - hsv[index] = newValue; - return Color.HSVToColor(hsv); - } - - public static String toHTML(int color) - { - return String.format("#%06X", 0xFFFFFF & color); - } } \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java b/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java index f8b5a9778..5381590f8 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java @@ -29,11 +29,11 @@ import com.activeandroid.Configuration; import org.isoron.uhabits.BuildConfig; import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Repetition; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.models.Streak; +import org.isoron.uhabits.models.sqlite.CheckmarkRecord; +import org.isoron.uhabits.models.sqlite.HabitRecord; +import org.isoron.uhabits.models.sqlite.RepetitionRecord; +import org.isoron.uhabits.models.sqlite.ScoreRecord; +import org.isoron.uhabits.models.sqlite.StreakRecord; import java.io.File; import java.io.IOException; @@ -41,17 +41,12 @@ import java.text.SimpleDateFormat; public abstract class DatabaseUtils { - public interface Command - { - void execute(); - } - - public static void executeAsTransaction(Command command) + public static void executeAsTransaction(Callback callback) { ActiveAndroid.beginTransaction(); try { - command.execute(); + callback.execute(); ActiveAndroid.setTransactionSuccessful(); } finally @@ -60,30 +55,18 @@ public abstract class DatabaseUtils } } - @SuppressWarnings("ResultOfMethodCallIgnored") - public static String saveDatabaseCopy(File dir) throws IOException - { - File db = getDatabaseFile(); - - SimpleDateFormat dateFormat = DateUtils.getBackupDateFormat(); - String date = dateFormat.format(DateUtils.getLocalTime()); - File dbCopy = new File(String.format("%s/Loop Habits Backup %s.db", dir.getAbsolutePath(), date)); - - FileUtils.copy(db, dbCopy); - - return dbCopy.getAbsolutePath(); - } - @NonNull public static File getDatabaseFile() { Context context = HabitsApplication.getContext(); - if(context == null) throw new RuntimeException("No application context found"); + if (context == null) + throw new RuntimeException("No application context found"); String databaseFilename = getDatabaseFilename(); return new File(String.format("%s/../databases/%s", - context.getApplicationContext().getFilesDir().getPath(), databaseFilename)); + context.getApplicationContext().getFilesDir().getPath(), + databaseFilename)); } @NonNull @@ -91,8 +74,7 @@ public abstract class DatabaseUtils { String databaseFilename = BuildConfig.databaseFilename; - if (HabitsApplication.isTestMode()) - databaseFilename = "test.db"; + if (HabitsApplication.isTestMode()) databaseFilename = "test.db"; return databaseFilename; } @@ -101,14 +83,15 @@ public abstract class DatabaseUtils public static void initializeActiveAndroid() { Context context = HabitsApplication.getContext(); - if(context == null) throw new RuntimeException("application context should not be null"); + if (context == null) throw new RuntimeException( + "application context should not be null"); Configuration dbConfig = new Configuration.Builder(context) - .setDatabaseName(getDatabaseFilename()) - .setDatabaseVersion(BuildConfig.databaseVersion) - .addModelClasses(Checkmark.class, Habit.class, Repetition.class, Score.class, - Streak.class) - .create(); + .setDatabaseName(getDatabaseFilename()) + .setDatabaseVersion(BuildConfig.databaseVersion) + .addModelClasses(CheckmarkRecord.class, HabitRecord.class, + RepetitionRecord.class, ScoreRecord.class, StreakRecord.class) + .create(); ActiveAndroid.initialize(dbConfig); } @@ -125,7 +108,28 @@ public abstract class DatabaseUtils } finally { - if(c != null) c.close(); + if (c != null) c.close(); } } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public static String saveDatabaseCopy(File dir) throws IOException + { + File db = getDatabaseFile(); + + SimpleDateFormat dateFormat = DateUtils.getBackupDateFormat(); + String date = dateFormat.format(DateUtils.getLocalTime()); + File dbCopy = new File( + String.format("%s/Loop Habits Backup %s.db", dir.getAbsolutePath(), + date)); + + FileUtils.copy(db, dbCopy); + + return dbCopy.getAbsolutePath(); + } + + public interface Callback + { + void execute(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java b/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java index 43d07c3f0..a770c6c7c 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java @@ -37,6 +37,7 @@ import android.util.Log; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import java.text.DateFormat; import java.util.Calendar; @@ -44,24 +45,20 @@ import java.util.Date; public abstract class ReminderUtils { - public static void createReminderAlarms(Context context) + public static void createReminderAlarm(Context context, + Habit habit, + @Nullable Long reminderTime) { - for (Habit habit : Habit.getHabitsWithReminder()) - createReminderAlarm(context, habit, null); - } - - public static void createReminderAlarm(Context context, Habit habit, @Nullable Long reminderTime) - { - if(!habit.hasReminder()) return; + if (!habit.hasReminder()) return; if (reminderTime == null) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); //noinspection ConstantConditions - calendar.set(Calendar.HOUR_OF_DAY, habit.reminderHour); + calendar.set(Calendar.HOUR_OF_DAY, habit.getReminderHour()); //noinspection ConstantConditions - calendar.set(Calendar.MINUTE, habit.reminderMin); + calendar.set(Calendar.MINUTE, habit.getReminderMin()); calendar.set(Calendar.SECOND, 0); reminderTime = calendar.getTimeInMillis(); @@ -70,7 +67,8 @@ public abstract class ReminderUtils reminderTime += AlarmManager.INTERVAL_DAY; } - long timestamp = DateUtils.getStartOfDay(DateUtils.toLocalTime(reminderTime)); + long timestamp = + DateUtils.getStartOfDay(DateUtils.toLocalTime(reminderTime)); Uri uri = habit.getUri(); @@ -80,22 +78,61 @@ public abstract class ReminderUtils alarmIntent.putExtra("timestamp", timestamp); alarmIntent.putExtra("reminderTime", reminderTime); - PendingIntent pendingIntent = - PendingIntent.getBroadcast(context, ((int) (habit.getId() % Integer.MAX_VALUE)) + 1, - alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, + ((int) (habit.getId() % Integer.MAX_VALUE)) + 1, alarmIntent, + PendingIntent.FLAG_UPDATE_CURRENT); - AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + AlarmManager manager = + (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (Build.VERSION.SDK_INT >= 23) - manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent); + manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, + reminderTime, pendingIntent); else if (Build.VERSION.SDK_INT >= 19) - manager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent); - else - manager.set(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent); + manager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, + pendingIntent); + else manager.set(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent); - String name = habit.name.substring(0, Math.min(3, habit.name.length())); + String name = habit.getName().substring(0, Math.min(3, habit.getName().length())); Log.d("ReminderHelper", String.format("Setting alarm (%s): %s", - DateFormat.getDateTimeInstance().format(new Date(reminderTime)), name)); + DateFormat.getDateTimeInstance().format(new Date(reminderTime)), + name)); + } + + public static void createReminderAlarms(Context context, + HabitList habitList) + { + for (Habit habit : habitList.getWithReminder()) + createReminderAlarm(context, habit, null); + } + + @Nullable + public static String getRingtoneName(Context context) + { + try + { + Uri ringtoneUri = getRingtoneUri(context); + String ringtoneName = + context.getResources().getString(R.string.none); + + if (ringtoneUri != null) + { + Ringtone ringtone = + RingtoneManager.getRingtone(context, ringtoneUri); + if (ringtone != null) + { + ringtoneName = ringtone.getTitle(context); + ringtone.stop(); + } + } + + return ringtoneName; + } + catch (RuntimeException e) + { + e.printStackTrace(); + return null; + } } @Nullable @@ -104,70 +141,57 @@ public abstract class ReminderUtils Uri ringtoneUri = null; Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI; - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - String prefRingtoneUri = prefs.getString("pref_ringtone_uri", defaultRingtoneUri.toString()); - if (prefRingtoneUri.length() > 0) ringtoneUri = Uri.parse(prefRingtoneUri); + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(context); + String prefRingtoneUri = + prefs.getString("pref_ringtone_uri", defaultRingtoneUri.toString()); + if (prefRingtoneUri.length() > 0) + ringtoneUri = Uri.parse(prefRingtoneUri); return ringtoneUri; } public static void parseRingtoneData(Context context, @Nullable Intent data) { - if(data == null) return; + if (data == null) return; - Uri ringtoneUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + Uri ringtoneUri = + data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); if (ringtoneUri != null) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - prefs.edit().putString("pref_ringtone_uri", ringtoneUri.toString()).apply(); + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(context); + prefs + .edit() + .putString("pref_ringtone_uri", ringtoneUri.toString()) + .apply(); } else { String off = context.getResources().getString(R.string.none); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(context); prefs.edit().putString("pref_ringtone_uri", "").apply(); } } - public static void startRingtonePickerActivity(Fragment fragment, int requestCode) + public static void startRingtonePickerActivity(Fragment fragment, + int requestCode) { - Uri existingRingtoneUri = ReminderUtils.getRingtoneUri(fragment.getContext()); + Uri existingRingtoneUri = + ReminderUtils.getRingtoneUri(fragment.getContext()); Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI; Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, + RingtoneManager.TYPE_NOTIFICATION); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultRingtoneUri); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existingRingtoneUri); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, + defaultRingtoneUri); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, + existingRingtoneUri); fragment.startActivityForResult(intent, requestCode); } - - @Nullable - public static String getRingtoneName(Context context) - { - try - { - Uri ringtoneUri = getRingtoneUri(context); - String ringtoneName = context.getResources().getString(R.string.none); - - if (ringtoneUri != null) - { - Ringtone ringtone = RingtoneManager.getRingtone(context, ringtoneUri); - if (ringtone != null) - { - ringtoneName = ringtone.getTitle(context); - ringtone.stop(); - } - } - - return ringtoneName; - } - catch (RuntimeException e) - { - e.printStackTrace(); - return null; - } - } } diff --git a/app/src/main/java/org/isoron/uhabits/utils/package-info.java b/app/src/main/java/org/isoron/uhabits/utils/package-info.java new file mode 100644 index 000000000..51db3c7a3 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/utils/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides various utilities classes, such as {@link org.isoron.uhabits.utils.ColorUtils}. + */ +package org.isoron.uhabits.utils; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/views/NumberView.java b/app/src/main/java/org/isoron/uhabits/views/NumberView.java deleted file mode 100644 index 7b788d12a..000000000 --- a/app/src/main/java/org/isoron/uhabits/views/NumberView.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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.views; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.text.Layout; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.util.AttributeSet; -import android.view.View; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.InterfaceUtils; - -public class NumberView extends View -{ - private int color; - private int number; - private float labelMarginTop; - private TextPaint pText; - private String label; - private RectF rect; - private StaticLayout labelLayout; - - private int width; - private int height; - - private float textSize; - private float labelTextSize; - private float numberTextSize; - private StaticLayout numberLayout; - - public NumberView(Context context) - { - super(context); - this.textSize = getResources().getDimension(R.dimen.regularTextSize); - init(); - } - - public NumberView(Context context, AttributeSet attrs) - { - super(context, attrs); - - this.textSize = getResources().getDimension(R.dimen.regularTextSize); - - this.label = InterfaceUtils.getAttribute(context, attrs, "label", "Number"); - this.number = InterfaceUtils.getIntAttribute(context, attrs, "number", 0); - this.textSize = InterfaceUtils.getFloatAttribute(context, attrs, "textSize", - getResources().getDimension(R.dimen.regularTextSize)); - - this.color = ColorUtils.getColor(getContext(), 7); - init(); - } - - public void setColor(int color) - { - this.color = color; - pText.setColor(color); - postInvalidate(); - } - - public void setLabel(String label) - { - this.label = label; - requestLayout(); - postInvalidate(); - } - - public void setNumber(int number) - { - this.number = number; - postInvalidate(); - } - - public void setTextSize(float textSize) - { - this.textSize = textSize; - requestLayout(); - postInvalidate(); - } - - private void init() - { - pText = new TextPaint(); - pText.setAntiAlias(true); - pText.setTextAlign(Paint.Align.CENTER); - - rect = new RectF(); - } - - @Override - @SuppressLint("DrawAllocation") - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - width = MeasureSpec.getSize(widthMeasureSpec); - height = MeasureSpec.getSize(heightMeasureSpec); - - labelTextSize = textSize; - labelMarginTop = textSize * 0.35f; - numberTextSize = textSize * 2.85f; - - pText.setTextSize(numberTextSize); - numberLayout = new StaticLayout(Integer.toString(number), pText, width, - Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f, false); - - int numberWidth = numberLayout.getWidth(); - int numberHeight = numberLayout.getHeight(); - - pText.setTextSize(labelTextSize); - labelLayout = new StaticLayout(label, pText, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0f, - false); - int labelWidth = labelLayout.getWidth(); - int labelHeight = labelLayout.getHeight(); - - width = Math.max(numberWidth, labelWidth); - height = (int) (numberHeight + labelHeight + labelMarginTop); - - setMeasuredDimension(width, height); - } - - @Override - protected void onDraw(Canvas canvas) - { - super.onDraw(canvas); - rect.set(0, 0, width, height); - - canvas.save(); - canvas.translate(rect.centerX(), 0); - pText.setColor(color); - pText.setTextSize(numberTextSize); - numberLayout.draw(canvas); - canvas.restore(); - - canvas.save(); - pText.setColor(Color.GRAY); - pText.setTextSize(labelTextSize); - canvas.translate(rect.centerX(), numberLayout.getHeight() + labelMarginTop); - labelLayout.draw(canvas); - canvas.restore(); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java deleted file mode 100644 index 0ddfcea58..000000000 --- a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.views; - -import android.content.Context; -import android.util.AttributeSet; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.models.Habit; - -import java.util.Calendar; -import java.util.GregorianCalendar; - -public class RepetitionCountView extends NumberView implements HabitDataView -{ - private int interval; - private Habit habit; - - public RepetitionCountView(Context context, AttributeSet attrs) - { - super(context, attrs); - this.interval = InterfaceUtils.getIntAttribute(context, attrs, "interval", 7); - int labelValue = InterfaceUtils.getIntAttribute(context, attrs, "labelValue", 7); - String labelFormat = InterfaceUtils.getAttribute(context, attrs, "labelFormat", - getResources().getString(R.string.last_x_days)); - - setLabel(String.format(labelFormat, labelValue)); - } - - @Override - public void refreshData() - { - if(isInEditMode()) - { - setNumber(interval); - return; - } - - long to = DateUtils.getStartOfToday(); - long from; - - if(interval == 0) - { - from = 0; - } - else - { - GregorianCalendar fromCalendar = DateUtils.getStartOfTodayCalendar(); - fromCalendar.add(Calendar.DAY_OF_YEAR, -interval + 1); - from = fromCalendar.getTimeInMillis(); - } - - if(habit != null) - setNumber(habit.repetitions.count(from, to)); - - postInvalidate(); - } - - @Override - public void setHabit(Habit habit) - { - this.habit = habit; - setColor(ColorUtils.getColor(getContext(), habit.color)); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java index 686255ece..a94809ff4 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -35,16 +35,23 @@ import android.widget.ImageView; import android.widget.RemoteViews; import android.widget.TextView; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.utils.InterfaceUtils; import java.io.FileOutputStream; import java.io.IOException; +import javax.inject.Inject; + public abstract class BaseWidgetProvider extends AppWidgetProvider { + @Inject + HabitList habitList; + private class WidgetDimensions { public int portraitWidth, portraitHeight; @@ -100,6 +107,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider private void updateWidget(Context context, AppWidgetManager manager, int widgetId, Bundle options) { + HabitsApplication.getComponent().inject(this); WidgetDimensions dim = getWidgetDimensions(context, options); Context appContext = context.getApplicationContext(); @@ -108,7 +116,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1L); if(habitId < 0) return; - Habit habit = Habit.get(habitId); + Habit habit = habitList.getById(habitId); if(habit == null) { drawErrorWidget(context, manager, widgetId); @@ -288,7 +296,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider widgetView.setDrawingCacheEnabled(true); widgetView.buildDrawingCache(true); Bitmap drawingCache = widgetView.getDrawingCache(); - remoteViews.setTextViewText(R.id.label, habit.name); + remoteViews.setTextViewText(R.id.label, habit.getName()); remoteViews.setImageViewBitmap(R.id.imageView, drawingCache); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) diff --git a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java index cc392ba7e..4447ac2c9 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java @@ -25,8 +25,8 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.views.CheckmarkWidgetView; -import org.isoron.uhabits.views.HabitDataView; +import org.isoron.uhabits.widgets.views.CheckmarkWidgetView; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; public class CheckmarkWidgetProvider extends BaseWidgetProvider { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java index 2fdbedb71..e186d3224 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java @@ -26,9 +26,9 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.views.GraphWidgetView; -import org.isoron.uhabits.views.HabitDataView; -import org.isoron.uhabits.views.HabitFrequencyView; +import org.isoron.uhabits.widgets.views.GraphWidgetView; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.HabitFrequencyView; public class FrequencyWidgetProvider extends BaseWidgetProvider { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java index 6082f0a2b..ffe682065 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HabitPickerDialog.java @@ -30,16 +30,23 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; +import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + public class HabitPickerDialog extends Activity implements AdapterView.OnItemClickListener { + @Inject + HabitList habitList; private Integer widgetId; + private ArrayList habitIds; @Override @@ -47,6 +54,7 @@ public class HabitPickerDialog extends Activity implements AdapterView.OnItemCli { super.onCreate(savedInstanceState); setContentView(R.layout.widget_configure_activity); + HabitsApplication.getComponent().inject(this); Intent intent = getIntent(); Bundle extras = intent.getExtras(); @@ -59,15 +67,15 @@ public class HabitPickerDialog extends Activity implements AdapterView.OnItemCli habitIds = new ArrayList<>(); ArrayList habitNames = new ArrayList<>(); - List habits = Habit.getAll(false); - for(Habit h : habits) + List habits = habitList.getAll(false); + for (Habit h : habits) { habitIds.add(h.getId()); - habitNames.add(h.name); + habitNames.add(h.getName()); } - ArrayAdapter adapter = - new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, habitNames); + ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, + habitNames); listView.setAdapter(adapter); listView.setOnItemClickListener(this); } @@ -77,8 +85,11 @@ public class HabitPickerDialog extends Activity implements AdapterView.OnItemCli { Long habitId = habitIds.get(position); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()); - prefs.edit().putLong(BaseWidgetProvider.getHabitIdKey(widgetId), habitId).commit(); + getApplicationContext()); + prefs + .edit() + .putLong(BaseWidgetProvider.getHabitIdKey(widgetId), habitId) + .commit(); WidgetManager.updateWidgets(this); diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java index bb8be7e25..b9d98f2e0 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java @@ -25,9 +25,9 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.views.GraphWidgetView; -import org.isoron.uhabits.views.HabitDataView; -import org.isoron.uhabits.views.HabitHistoryView; +import org.isoron.uhabits.widgets.views.GraphWidgetView; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView; public class HistoryWidgetProvider extends BaseWidgetProvider { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java index a046dc70d..ee998de8d 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java @@ -24,11 +24,11 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.views.GraphWidgetView; -import org.isoron.uhabits.views.HabitDataView; -import org.isoron.uhabits.views.HabitScoreView; +import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.widgets.views.GraphWidgetView; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.HabitScoreView; public class ScoreWidgetProvider extends BaseWidgetProvider { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java index f0455d00a..59af7a256 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java @@ -25,9 +25,9 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.views.GraphWidgetView; -import org.isoron.uhabits.views.HabitDataView; -import org.isoron.uhabits.views.HabitStreakView; +import org.isoron.uhabits.widgets.views.GraphWidgetView; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.HabitStreakView; public class StreakWidgetProvider extends BaseWidgetProvider { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/package-info.java b/app/src/main/java/org/isoron/uhabits/widgets/package-info.java new file mode 100644 index 000000000..5616e64ad --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/widgets/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides home-screen Android widgets and related classes. + */ +package org.isoron.uhabits.widgets; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java b/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java similarity index 74% rename from app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java rename to app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java index 0f6046e1b..47a8b2587 100644 --- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.widgets.views; import android.content.Context; import android.support.annotation.NonNull; @@ -27,22 +27,29 @@ import android.util.TypedValue; import android.widget.TextView; import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.HabitWidgetView; +import org.isoron.uhabits.ui.habits.show.views.RingView; +import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.InterfaceUtils; -public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataView +public class CheckmarkWidgetView extends HabitWidgetView + implements HabitDataView { private int activeColor; + private float percentage; @Nullable private String name; private RingView ring; + private TextView label; + private int checkmarkValue; public CheckmarkWidgetView(Context context) @@ -57,32 +64,6 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie init(); } - private void init() - { - ring = (RingView) findViewById(R.id.scoreRing); - label = (TextView) findViewById(R.id.label); - - if(ring != null) ring.setIsTransparencyEnabled(true); - - if(isInEditMode()) - { - percentage = 0.75f; - name = "Wake up early"; - activeColor = ColorUtils.CSV_PALETTE[6]; - checkmarkValue = Checkmark.CHECKED_EXPLICITLY; - refresh(); - } - } - - @Override - public void setHabit(@NonNull Habit habit) - { - super.setHabit(habit); - this.name = habit.name; - this.activeColor = ColorUtils.getColor(getContext(), habit.color); - refresh(); - } - public void refresh() { if (backgroundPaint == null || frame == null || ring == null) return; @@ -98,8 +79,8 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie case Checkmark.CHECKED_EXPLICITLY: text = getResources().getString(R.string.fa_check); backgroundColor = activeColor; - foregroundColor = - InterfaceUtils.getStyledColor(context, R.attr.highContrastReverseTextColor); + foregroundColor = InterfaceUtils.getStyledColor(context, + R.attr.highContrastReverseTextColor); setShadowAlpha(0x4f); rebuildBackground(); @@ -110,15 +91,19 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie case Checkmark.CHECKED_IMPLICITLY: text = getResources().getString(R.string.fa_check); - backgroundColor = InterfaceUtils.getStyledColor(context, R.attr.cardBackgroundColor); - foregroundColor = InterfaceUtils.getStyledColor(context, R.attr.mediumContrastTextColor); + backgroundColor = InterfaceUtils.getStyledColor(context, + R.attr.cardBackgroundColor); + foregroundColor = InterfaceUtils.getStyledColor(context, + R.attr.mediumContrastTextColor); break; case Checkmark.UNCHECKED: default: text = getResources().getString(R.string.fa_times); - backgroundColor = InterfaceUtils.getStyledColor(context, R.attr.cardBackgroundColor); - foregroundColor = InterfaceUtils.getStyledColor(context, R.attr.mediumContrastTextColor); + backgroundColor = InterfaceUtils.getStyledColor(context, + R.attr.cardBackgroundColor); + foregroundColor = InterfaceUtils.getStyledColor(context, + R.attr.mediumContrastTextColor); break; } @@ -134,6 +119,49 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie postInvalidate(); } + @Override + public void refreshData() + { + if (habit == null) return; + this.percentage = + (float) habit.getScores().getTodayValue() / Score.MAX_VALUE; + this.checkmarkValue = habit.getCheckmarks().getTodayValue(); + refresh(); + } + + @Override + public void setHabit(@NonNull Habit habit) + { + super.setHabit(habit); + this.name = habit.getName(); + this.activeColor = ColorUtils.getColor(getContext(), habit.getColor()); + refresh(); + } + + @Override + @NonNull + protected Integer getInnerLayoutId() + { + return R.layout.widget_checkmark; + } + + private void init() + { + ring = (RingView) findViewById(R.id.scoreRing); + label = (TextView) findViewById(R.id.label); + + if (ring != null) ring.setIsTransparencyEnabled(true); + + if (isInEditMode()) + { + percentage = 0.75f; + name = "Wake up early"; + activeColor = ColorUtils.getAndroidTestColor(6); + checkmarkValue = Checkmark.CHECKED_EXPLICITLY; + refresh(); + } + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @@ -147,16 +175,18 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie w *= scale; h *= scale; - if(h < getResources().getDimension(R.dimen.checkmarkWidget_heightBreakpoint)) - ring.setVisibility(GONE); - else - ring.setVisibility(VISIBLE); + if (h < getResources().getDimension( + R.dimen.checkmarkWidget_heightBreakpoint)) ring.setVisibility(GONE); + else ring.setVisibility(VISIBLE); - widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) w, MeasureSpec.EXACTLY); - heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) h, MeasureSpec.EXACTLY); + widthMeasureSpec = + MeasureSpec.makeMeasureSpec((int) w, MeasureSpec.EXACTLY); + heightMeasureSpec = + MeasureSpec.makeMeasureSpec((int) h, MeasureSpec.EXACTLY); float textSize = 0.15f * h; - float maxTextSize = getResources().getDimension(R.dimen.smallerTextSize); + float maxTextSize = + getResources().getDimension(R.dimen.smallerTextSize); textSize = Math.min(textSize, maxTextSize); label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); @@ -165,19 +195,4 @@ public class CheckmarkWidgetView extends HabitWidgetView implements HabitDataVie super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - - @Override - public void refreshData() - { - if(habit == null) return; - this.percentage = (float) habit.scores.getTodayValue() / Score.MAX_VALUE; - this.checkmarkValue = habit.checkmarks.getTodayValue(); - refresh(); - } - - @NonNull - protected Integer getInnerLayoutId() - { - return R.layout.widget_checkmark; - } } diff --git a/app/src/main/java/org/isoron/uhabits/views/GraphWidgetView.java b/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java similarity index 91% rename from app/src/main/java/org/isoron/uhabits/views/GraphWidgetView.java rename to app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java index bb142ca7d..4198d84b1 100644 --- a/app/src/main/java/org/isoron/uhabits/views/GraphWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.views; +package org.isoron.uhabits.widgets.views; import android.content.Context; import android.support.annotation.NonNull; @@ -27,6 +27,8 @@ import android.widget.TextView; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.HabitWidgetView; public class GraphWidgetView extends HabitWidgetView implements HabitDataView { @@ -60,7 +62,7 @@ public class GraphWidgetView extends HabitWidgetView implements HabitDataView { super.setHabit(habit); dataView.setHabit(habit); - title.setText(habit.name); + title.setText(habit.getName()); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/widgets/views/package-info.java b/app/src/main/java/org/isoron/uhabits/widgets/views/package-info.java new file mode 100644 index 000000000..2fbf2799c --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides views that are specific for the home-screen widgets. + */ +package org.isoron.uhabits.widgets.views; \ No newline at end of file diff --git a/app/src/main/res/layout/list_habits_card.xml b/app/src/main/res/layout/list_habits_card.xml index 3320a995c..2aef23840 100644 --- a/app/src/main/res/layout/list_habits_card.xml +++ b/app/src/main/res/layout/list_habits_card.xml @@ -29,7 +29,7 @@ style="@style/ListHabits.HabitCard" android:layout_width="match_parent"> - - - - @@ -272,7 +272,7 @@ style="@style/CardHeader" android:text="@string/best_streaks"/> - @@ -287,7 +287,7 @@ style="@style/CardHeader" android:text="@string/frequency"/> - diff --git a/app/src/main/res/layout/widget_checkmark.xml b/app/src/main/res/layout/widget_checkmark.xml index a6d0c01b7..da88149a4 100644 --- a/app/src/main/res/layout/widget_checkmark.xml +++ b/app/src/main/res/layout/widget_checkmark.xml @@ -27,7 +27,7 @@ android:gravity="center" android:orientation="vertical"> - + * + * 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; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.utils.DateUtils; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringWriter; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY; +import static org.isoron.uhabits.models.Checkmark.CHECKED_IMPLICITLY; +import static org.isoron.uhabits.models.Checkmark.UNCHECKED; + +public class CheckmarkListTest extends BaseUnitTest +{ + private Habit nonDailyHabit; + + private Habit emptyHabit; + + @Override + public void setUp() + { + super.setUp(); + + fixtures.createShortHabit(); + nonDailyHabit = fixtures.createShortHabit(); + emptyHabit = fixtures.createEmptyHabit(); + } + + @Test + public void test_getAllValues_moveBackwardsInTime() + { + travelInTime(-3); + + int[] expectedValues = { + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY, + UNCHECKED, + CHECKED_IMPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY + }; + + int[] actualValues = nonDailyHabit.getCheckmarks().getAllValues(); + + assertThat(actualValues, equalTo(expectedValues)); + } + + + @Test + public void test_getAllValues_moveForwardInTime() + { + travelInTime(3); + + int[] expectedValues = { + UNCHECKED, + UNCHECKED, + UNCHECKED, + CHECKED_EXPLICITLY, + UNCHECKED, + CHECKED_IMPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY, + UNCHECKED, + CHECKED_IMPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY + }; + + int[] actualValues = nonDailyHabit.getCheckmarks().getAllValues(); + + assertThat(actualValues, equalTo(expectedValues)); + } + + @Test + public void test_getAllValues_withEmptyHabit() + { + int[] expectedValues = new int[0]; + int[] actualValues = emptyHabit.getCheckmarks().getAllValues(); + + assertThat(actualValues, equalTo(expectedValues)); + } + + @Test + public void test_getAllValues_withNonDailyHabit() + { + int[] expectedValues = { + CHECKED_EXPLICITLY, + UNCHECKED, + CHECKED_IMPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY, + UNCHECKED, + CHECKED_IMPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY + }; + + int[] actualValues = nonDailyHabit.getCheckmarks().getAllValues(); + + assertThat(actualValues, equalTo(expectedValues)); + } + + @Test + public void test_getTodayValue() + { + travelInTime(-1); + assertThat(nonDailyHabit.getCheckmarks().getTodayValue(), + equalTo(UNCHECKED)); + + travelInTime(0); + assertThat(nonDailyHabit.getCheckmarks().getTodayValue(), + equalTo(CHECKED_EXPLICITLY)); + + travelInTime(1); + assertThat(nonDailyHabit.getCheckmarks().getTodayValue(), + equalTo(UNCHECKED)); + } + + @Test + public void test_getValues_withInvalidInterval() + { + int values[] = nonDailyHabit.getCheckmarks().getValues(100L, -100L); + assertThat(values, equalTo(new int[0])); + } + + @Test + public void test_getValues_withValidInterval() + { + long from = + DateUtils.getStartOfToday() - 15 * DateUtils.millisecondsInOneDay; + long to = + DateUtils.getStartOfToday() - 5 * DateUtils.millisecondsInOneDay; + + int[] expectedValues = { + CHECKED_EXPLICITLY, + UNCHECKED, + CHECKED_IMPLICITLY, + CHECKED_EXPLICITLY, + CHECKED_EXPLICITLY, + UNCHECKED, + UNCHECKED, + UNCHECKED, + UNCHECKED, + UNCHECKED, + UNCHECKED + }; + + int[] actualValues = nonDailyHabit.getCheckmarks().getValues(from, to); + + assertThat(actualValues, equalTo(expectedValues)); + } + + @Test + public void test_writeCSV() throws IOException + { + String expectedCSV = "2015-01-25,2\n" + + "2015-01-24,0\n" + + "2015-01-23,1\n" + + "2015-01-22,2\n" + + "2015-01-21,2\n" + + "2015-01-20,2\n" + + "2015-01-19,0\n" + + "2015-01-18,1\n" + + "2015-01-17,2\n" + + "2015-01-16,2\n"; + + + StringWriter writer = new StringWriter(); + nonDailyHabit.getCheckmarks().writeCSV(writer); + + assertThat(writer.toString(), equalTo(expectedCSV)); + } + + private void travelInTime(int days) + { + DateUtils.setFixedLocalTime( + FIXED_LOCAL_TIME + days * DateUtils.millisecondsInOneDay); + } +} diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitFixtures.java b/app/src/test/java/org/isoron/uhabits/models/HabitFixtures.java new file mode 100644 index 000000000..70f7f1131 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/models/HabitFixtures.java @@ -0,0 +1,92 @@ +/* + * 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; + +import org.isoron.uhabits.utils.DateUtils; + +public class HabitFixtures +{ + public boolean NON_DAILY_HABIT_CHECKS[] = { + true, false, false, true, true, true, false, false, true, true + }; + + private final HabitList habitList; + + public HabitFixtures(HabitList habitList) + { + this.habitList = habitList; + } + + public Habit createEmptyHabit() + { + Habit habit = new Habit(); + habit.setName("Meditate"); + habit.setDescription("Did you meditate this morning?"); + habit.setColor(3); + habit.setFreqNum(1); + habit.setFreqDen(1); + habitList.add(habit); + return habit; + } + + public Habit createLongHabit() + { + Habit habit = createEmptyHabit(); + habit.setFreqNum(3); + habit.setFreqDen(7); + habit.setColor(4); + + long day = DateUtils.millisecondsInOneDay; + long today = DateUtils.getStartOfToday(); + int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, + 28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, + 81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120}; + + for (int mark : marks) + habit.getRepetitions().toggleTimestamp(today - mark * day); + + return habit; + } + + public Habit createShortHabit() + { + Habit habit = new Habit(); + habit.setName("Wake up early"); + habit.setDescription("Did you wake up before 6am?"); + habit.setFreqNum(2); + habit.setFreqDen(3); + habitList.add(habit); + + long timestamp = DateUtils.getStartOfToday(); + for (boolean c : NON_DAILY_HABIT_CHECKS) + { + if (c) habit.getRepetitions().toggleTimestamp(timestamp); + timestamp -= DateUtils.millisecondsInOneDay; + } + + return habit; + } + + public void purgeHabits() + { + for (Habit h : habitList.getAll(true)) + habitList.remove(h); + } +} diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java b/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java new file mode 100644 index 000000000..a79ad5959 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java @@ -0,0 +1,200 @@ +/* + * 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; + +import org.hamcrest.MatcherAssert; +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.utils.DateUtils; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.Assert.fail; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; + +public class HabitListTest extends BaseUnitTest +{ + private HabitList list; + + private ArrayList habits; + + @Override + public void setUp() + { + super.setUp(); + + list = modelFactory.buildHabitList(); + habits = new ArrayList<>(); + + for (int i = 0; i < 10; i++) + { + Habit habit = new Habit(); + habit.setId((long) i); + habits.add(habit); + list.add(habit); + + if (i % 3 == 0) + { + habit.setReminderDays(DateUtils.ALL_WEEK_DAYS); + habit.setReminderHour(8); + habit.setReminderMin(30); + } + } + + habits.get(0).setArchived(1); + habits.get(1).setArchived(1); + habits.get(4).setArchived(1); + habits.get(7).setArchived(1); + } + + @Test + public void test_count() + { + assertThat(list.count(), equalTo(6)); + } + + @Test + public void test_countWithArchived() + { + assertThat(list.countWithArchived(), equalTo(10)); + } + + @Test + public void test_getAll() + { + List filteredList = list.getAll(false); + + assertThat(filteredList.size(), equalTo(6)); + assertThat(filteredList.contains(habits.get(2)), is(true)); + assertThat(filteredList.contains(habits.get(4)), is(false)); + + filteredList = list.getAll(true); + + assertThat(filteredList.size(), equalTo(10)); + assertThat(filteredList.contains(habits.get(2)), is(true)); + assertThat(filteredList.contains(habits.get(4)), is(true)); + } + + @Test + public void test_getByPosition() + { + assertThat(list.getByPosition(0), equalTo(habits.get(0))); + assertThat(list.getByPosition(3), equalTo(habits.get(3))); + assertThat(list.getByPosition(9), equalTo(habits.get(9))); + } + + @Test + public void test_getHabitsWithReminder() + { + List filtered = list.getWithReminder(); + assertThat(filtered.size(), equalTo(4)); + assertThat(filtered.contains(habits.get(0)), equalTo(true)); + assertThat(filtered.contains(habits.get(1)), equalTo(false)); + } + + @Test + public void test_get_withInvalidId() + { + assertThat(list.getById(100L), is(nullValue())); + } + + @Test + public void test_get_withValidId() + { + Habit habit1 = habits.get(0); + Habit habit2 = list.getById(habit1.getId()); + assertThat(habit1, equalTo(habit2)); + } + + @Test + public void test_reorder() + { + int operations[][] = { + {5, 2}, {3, 7}, {4, 4}, {3, 2} + }; + + int expectedPosition[][] = { + {0, 1, 3, 4, 5, 2, 6, 7, 8, 9}, + {0, 1, 7, 3, 4, 2, 5, 6, 8, 9}, + {0, 1, 7, 3, 4, 2, 5, 6, 8, 9}, + {0, 1, 7, 2, 4, 3, 5, 6, 8, 9}, + }; + + for (int i = 0; i < operations.length; i++) + { + int from = operations[i][0]; + int to = operations[i][1]; + + Habit fromHabit = list.getByPosition(from); + Habit toHabit = list.getByPosition(to); + list.reorder(fromHabit, toHabit); + + int actualPositions[] = new int[10]; + + for (int j = 0; j < 10; j++) + { + Habit h = list.getById(j); + if (h == null) fail(); + actualPositions[j] = list.indexOf(h); + } + + assertThat(actualPositions, equalTo(expectedPosition[i])); + } + } + + @Test + public void test_writeCSV() throws IOException + { + HabitList list = modelFactory.buildHabitList(); + + Habit h1 = new Habit(); + h1.setName("Meditate"); + h1.setDescription("Did you meditate this morning?"); + h1.setFreqNum(1); + h1.setFreqDen(1); + h1.setColor(3); + + Habit h2 = new Habit(); + h2.setName("Wake up early"); + h2.setDescription("Did you wake up before 6am?"); + h2.setFreqNum(2); + h2.setFreqDen(3); + h2.setColor(5); + + list.add(h1); + list.add(h2); + + String expectedCSV = + "Position,Name,Description,NumRepetitions,Interval,Color\n" + + "001,Meditate,Did you meditate this morning?,1,1,#AFB42B\n" + + "002,Wake up early,Did you wake up before 6am?,2,3,#00897B\n"; + + StringWriter writer = new StringWriter(); + list.writeCSV(writer); + + MatcherAssert.assertThat(writer.toString(), equalTo(expectedCSV)); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitTest.java b/app/src/test/java/org/isoron/uhabits/models/HabitTest.java new file mode 100644 index 000000000..443e97cf8 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/models/HabitTest.java @@ -0,0 +1,114 @@ +/* + * 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; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.utils.DateUtils; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; + +public class HabitTest extends BaseUnitTest +{ + + @Test + public void testConstructor_default() + { + Habit habit = new Habit(); + assertThat(habit.getArchived(), is(0)); + assertThat(habit.getHighlight(), is(0)); + + assertThat(habit.getReminderHour(), is(nullValue())); + assertThat(habit.getReminderMin(), is(nullValue())); + + assertThat(habit.getReminderDays(), is(not(nullValue()))); +// assertThat(habit.getStreaks(), is(not(nullValue()))); +// assertThat(habit.getScores(), is(not(nullValue()))); + assertThat(habit.getRepetitions(), is(not(nullValue()))); + assertThat(habit.getCheckmarks(), is(not(nullValue()))); + } + + @Test + public void test_copyAttributes() + { + Habit model = new Habit(); + model.setArchived(1); + model.setHighlight(1); + model.setColor(0); + model.setFreqNum(10); + model.setFreqDen(20); + model.setReminderDays(1); + model.setReminderHour(8); + model.setReminderMin(30); + + Habit habit = new Habit(); + habit.copyFrom(model); + assertThat(habit.getArchived(), is(model.getArchived())); + assertThat(habit.getHighlight(), is(model.getHighlight())); + assertThat(habit.getColor(), is(model.getColor())); + assertThat(habit.getFreqNum(), is(model.getFreqNum())); + assertThat(habit.getFreqDen(), is(model.getFreqDen())); + assertThat(habit.getReminderDays(), is(model.getReminderDays())); + assertThat(habit.getReminderHour(), is(model.getReminderHour())); + assertThat(habit.getReminderMin(), is(model.getReminderMin())); + } + +// @Test +// public void test_rebuildOrder() +// { +// List ids = new LinkedList<>(); +// int originalPositions[] = { 0, 1, 1, 4, 6, 8, 10, 10, 13}; +// +// for (int p : originalPositions) +// { +// Habit h = new Habit(); +// habitList.insert(h); +// ids.add(h.getId()); +// } +// +// ((SQLiteHabitList) habitList).rebuildOrder(); +// +// for (int i = 0; i < originalPositions.length; i++) +// { +// Habit h = habitList.get(ids.get(i)); +// if(h == null) fail(); +// assertThat(habitList.indexOf(h), is(i)); +// } +// } + + + @Test + public void test_hasReminder_clearReminder() + { + Habit h = new Habit(); + assertThat(h.hasReminder(), is(false)); + + h.setReminderDays(DateUtils.ALL_WEEK_DAYS); + h.setReminderHour(8); + h.setReminderMin(30); + assertThat(h.hasReminder(), is(true)); + + h.clearReminder(); + assertThat(h.hasReminder(), is(false)); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/models/RepetitionListTest.java b/app/src/test/java/org/isoron/uhabits/models/RepetitionListTest.java new file mode 100644 index 000000000..51dae4c14 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/models/RepetitionListTest.java @@ -0,0 +1,179 @@ +/* + * 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; + +import android.support.annotation.NonNull; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.utils.DateUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Random; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +public class RepetitionListTest extends BaseUnitTest +{ + @NonNull + private RepetitionList reps; + + @NonNull + private Habit habit; + + private long today; + + private long day; + + @NonNull + private ModelObservable.Listener listener; + + @Override + @Before + public void setUp() + { + super.setUp(); + habit = new Habit(); + reps = habit.getRepetitions(); + + today = DateUtils.getStartOfToday(); + day = DateUtils.millisecondsInOneDay; + + reps.toggleTimestamp(today - 3 * day); + reps.toggleTimestamp(today - 2 * day); + reps.toggleTimestamp(today); + reps.toggleTimestamp(today - 7 * day); + reps.toggleTimestamp(today - 5 * day); + + listener = mock(ModelObservable.Listener.class); + reps.getObservable().addListener(listener); + } + + @Override + @After + public void tearDown() + { + super.tearDown(); + } + + @Test + public void test_contains() + { + assertThat(reps.containsTimestamp(today), is(true)); + assertThat(reps.containsTimestamp(today - 2 * day), is(true)); + assertThat(reps.containsTimestamp(today - 3 * day), is(true)); + + assertThat(reps.containsTimestamp(today - day), is(false)); + assertThat(reps.containsTimestamp(today - 4 * day), is(false)); + } + + @Test + public void test_getOldest() + { + Repetition rep = reps.getOldest(); + assertThat(rep.getTimestamp(), is(equalTo(today - 7 * day))); + } + + @Test + public void test_getWeekDayFrequency() + { + habit = new Habit(); + reps = habit.getRepetitions(); + + Random random = new Random(); + Integer weekdayCount[][] = new Integer[12][7]; + Integer monthCount[] = new Integer[12]; + + Arrays.fill(monthCount, 0); + for (Integer row[] : weekdayCount) + Arrays.fill(row, 0); + + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); + + // Sets the current date to the end of November + day.set(2015, 10, 30); + DateUtils.setFixedLocalTime(day.getTimeInMillis()); + + // Add repetitions randomly from January to December + // Leaves the month of March empty, to check that it returns null + day.set(2015, 0, 1); + for (int i = 0; i < 365; i++) + { + if (random.nextBoolean()) + { + int month = day.get(Calendar.MONTH); + int week = day.get(Calendar.DAY_OF_WEEK) % 7; + + if (month != 2) + { + if (month <= 10) + { + weekdayCount[month][week]++; + monthCount[month]++; + } + reps.toggleTimestamp(day.getTimeInMillis()); + } + } + + day.add(Calendar.DAY_OF_YEAR, 1); + } + + HashMap freq = + reps.getWeekdayFrequency(); + + // Repetitions until November should be counted correctly + for (int month = 0; month < 11; month++) + { + day.set(2015, month, 1); + Integer actualCount[] = freq.get(day.getTimeInMillis()); + if (monthCount[month] == 0) assertThat(actualCount, equalTo(null)); + else assertThat(actualCount, equalTo(weekdayCount[month])); + } + + // Repetitions in December should be discarded + day.set(2015, 11, 1); + assertThat(freq.get(day.getTimeInMillis()), equalTo(null)); + } + + @Test + public void test_toggleTimestamp() + { + assertThat(reps.containsTimestamp(today), equalTo(true)); + reps.toggleTimestamp(today); + assertThat(reps.containsTimestamp(today), equalTo(false)); + verify(listener).onModelChange(); + reset(listener); + + assertThat(reps.containsTimestamp(today - day), equalTo(false)); + reps.toggleTimestamp(today - day); + assertThat(reps.containsTimestamp(today - day), equalTo(true)); + verify(listener).onModelChange(); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java b/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java new file mode 100644 index 000000000..3107b6a1b --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java @@ -0,0 +1,149 @@ +/* + * 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; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.utils.DateUtils; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class StreakListTest extends BaseUnitTest +{ + private Habit habit; + + private StreakList streaks; + + private long day; + + private long today; + + private ModelObservable.Listener listener; + + @Override + public void setUp() + { + super.setUp(); + habit = fixtures.createLongHabit(); + habit.setFreqDen(1); + habit.setFreqNum(1); + + streaks = habit.getStreaks(); + streaks.rebuild(); + + listener = mock(ModelObservable.Listener.class); + streaks.getObservable().addListener(listener); + + today = DateUtils.getStartOfToday(); + day = DateUtils.millisecondsInOneDay; + } + + @Test + public void testFindBeginning_withEmptyHistory() + { + Habit habit2 = fixtures.createEmptyHabit(); + Long beginning = habit2.getStreaks().findBeginning(); + assertThat(beginning, is(nullValue())); + } + + @Test + public void testFindBeginning_withLongHistory() + { + streaks.rebuild(); + assertThat(streaks.findBeginning(), equalTo(today - day)); + + streaks.invalidateNewerThan(today - 20 * day); + assertThat(streaks.findBeginning(), equalTo(today - 28 * day)); + + streaks.invalidateNewerThan(0); + assertThat(streaks.findBeginning(), equalTo(today - 120 * day)); + } + + @Test + public void testGetAll() throws Exception + { + List all = streaks.getAll(); + + assertThat(all.size(), equalTo(22)); + + assertThat(all.get(3).getEnd(), equalTo(today - 7 * day)); + assertThat(all.get(3).getStart(), equalTo(today - 10 * day)); + + assertThat(all.get(17).getEnd(), equalTo(today - 89 * day)); + assertThat(all.get(17).getStart(), equalTo(today - 91 * day)); + } + + @Test + public void testGetBest() throws Exception + { + List best = streaks.getBest(4); + assertThat(best.size(), equalTo(4)); + + assertThat(best.get(0).getLength(), equalTo(4L)); + assertThat(best.get(1).getLength(), equalTo(3L)); + assertThat(best.get(2).getLength(), equalTo(5L)); + assertThat(best.get(3).getLength(), equalTo(6L)); + + best = streaks.getBest(2); + assertThat(best.size(), equalTo(2)); + + assertThat(best.get(0).getLength(), equalTo(5L)); + assertThat(best.get(1).getLength(), equalTo(6L)); + } + + @Test + public void testGetNewestComputed() throws Exception + { + Streak s = streaks.getNewestComputed(); + assertThat(s.getEnd(), equalTo(today)); + assertThat(s.getStart(), equalTo(today - day)); + + streaks.invalidateNewerThan(today - 8 * day); + + s = streaks.getNewestComputed(); + assertThat(s.getEnd(), equalTo(today - 12 * day)); + assertThat(s.getStart(), equalTo(today - 12 * day)); + } + + @Test + public void testInvalidateNewer() + { + Streak s = streaks.getNewestComputed(); + assertThat(s.getEnd(), equalTo(today)); + + streaks.invalidateNewerThan(today - 8 * day); + verify(listener).onModelChange(); + + s = streaks.getNewestComputed(); + assertThat(s.getEnd(), equalTo(today - 12 * day)); + } + + private void log(List sl) + { + for (Streak s : sl) + log("%5d -- %5d (%d)", (today - s.getEnd()) / day, + (today - s.getStart()) / day, s.getLength()); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 0382b84b4..aa8cddbe1 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.1.2' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.2' classpath 'me.tatarka:gradle-retrolambda:3.2.5' @@ -19,15 +19,3 @@ allprojects { maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } } - -project.ext.preDexLibs = !project.hasProperty('disablePreDex') - -subprojects { - project.plugins.whenPluginAdded { plugin -> - if ("com.android.build.gradle.AppPlugin".equals(plugin.class.name)) { - project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs - } else if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)) { - project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs - } - } -} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d57051703..dd1562ebd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Thu Jun 09 17:53:47 EDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip From 2b23b36e36703842cf589f36d75cbfb6cecb06d0 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 10 Jun 2016 18:54:47 -0400 Subject: [PATCH 017/184] Move remaining model tests to JVM; simplify SQLite implementation --- app/build.gradle | 11 + .../uhabits/unit/models/ScoreListTest.java | 163 ---------- .../org/isoron/uhabits/BaseComponent.java | 4 +- .../org/isoron/uhabits/models/Checkmark.java | 11 +- .../org/isoron/uhabits/models/HabitList.java | 9 +- .../isoron/uhabits/models/RepetitionList.java | 6 +- .../java/org/isoron/uhabits/models/Score.java | 9 +- .../org/isoron/uhabits/models/ScoreList.java | 125 ++++---- .../org/isoron/uhabits/models/StreakList.java | 9 +- .../models/memory/MemoryModelFactory.java | 2 +- .../models/memory/MemoryScoreList.java | 96 ++++++ .../models/sqlite/SQLiteCheckmarkList.java | 1 + .../models/sqlite/SQLiteHabitList.java | 4 +- .../models/sqlite/SQLiteScoreList.java | 62 ++-- .../models/sqlite/SQLiteStreakList.java | 12 +- .../ui/habits/edit/BaseDialogFragment.java | 11 +- .../habits/edit/EditHabitDialogFragment.java | 9 - .../ui/habits/show/views/HabitScoreView.java | 60 +++- .../org/isoron/uhabits/utils/DateUtils.java | 288 ++++++++++-------- .../isoron/uhabits/models/ScoreListTest.java | 195 ++++++++++++ .../org/isoron/uhabits}/models/ScoreTest.java | 46 ++- .../isoron/uhabits/utils/DateUtilsTest.java | 146 +++++++++ 22 files changed, 817 insertions(+), 462 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java create mode 100644 app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java rename app/src/{androidTest/java/org/isoron/uhabits/unit => test/java/org/isoron/uhabits}/models/ScoreTest.java (71%) create mode 100644 app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java diff --git a/app/build.gradle b/app/build.gradle index 318bc5d6c..93b68ca48 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,6 +35,17 @@ android { targetCompatibility 1.8 sourceCompatibility 1.8 } + + testOptions { + unitTests.all { + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } + } dependencies { diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java index fdda41504..d64cd3aea 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java @@ -23,174 +23,11 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.DatabaseUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; import org.junit.runner.RunWith; -import java.io.IOException; -import java.io.StringWriter; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - @RunWith(AndroidJUnit4.class) @SmallTest public class ScoreListTest extends BaseAndroidTest { - private Habit habit; - - @Before - public void setUp() - { - super.setUp(); - - habitFixtures.purgeHabits(habitList); - habit = habitFixtures.createEmptyHabit(); - } - - @After - public void tearDown() - { - DateUtils.setFixedLocalTime(null); - } - - @Test - public void test_getAllValues_withGroups() - { - toggleRepetitions(0, 20); - - int expectedValues[] = {11434978, 7894999, 3212362}; - - int actualValues[] = habit.getScores().getAllValues(7); - assertThat(actualValues, equalTo(expectedValues)); - } - - @Test - public void test_getAllValues_withoutGroups() - { - toggleRepetitions(0, 20); - - int expectedValues[] = { - 12629351, - 12266245, - 11883254, - 11479288, - 11053198, - 10603773, - 10129735, - 9629735, - 9102352, - 8546087, - 7959357, - 7340494, - 6687738, - 5999234, - 5273023, - 4507040, - 3699107, - 2846927, - 1948077, - 1000000 - }; - - int actualValues[] = habit.getScores().getAllValues(1); - assertThat(actualValues, equalTo(expectedValues)); - } - - @Test - public void test_getTodayValue() - { - toggleRepetitions(0, 20); - assertThat(habit.getScores().getTodayValue(), equalTo(12629351)); - } - - @Test - public void test_getValue() - { - toggleRepetitions(0, 20); - - int expectedValues[] = { - 12629351, - 12266245, - 11883254, - 11479288, - 11053198, - 10603773, - 10129735, - 9629735, - 9102352, - 8546087, - 7959357, - 7340494, - 6687738, - 5999234, - 5273023, - 4507040, - 3699107, - 2846927, - 1948077, - 1000000 - }; - - long current = DateUtils.getStartOfToday(); - for (int expectedValue : expectedValues) - { - assertThat(habit.getScores().getValue(current), - equalTo(expectedValue)); - current -= DateUtils.millisecondsInOneDay; - } - } - - @Test - public void test_invalidateNewerThan() - { - assertThat(habit.getScores().getTodayValue(), equalTo(0)); - - toggleRepetitions(0, 2); - assertThat(habit.getScores().getTodayValue(), equalTo(1948077)); - - habit.setFreqNum(1); - habit.setFreqDen(2); - habit.getScores().invalidateNewerThan(0); - - assertThat(habit.getScores().getTodayValue(), equalTo(1974654)); - } - - @Test - public void test_writeCSV() throws IOException - { - habitFixtures.purgeHabits(habitList); - Habit habit = habitFixtures.createShortHabit(); - - String expectedCSV = "2015-01-16,0.0519\n" + - "2015-01-17,0.1021\n" + - "2015-01-18,0.0986\n" + - "2015-01-19,0.0952\n" + - "2015-01-20,0.1439\n" + - "2015-01-21,0.1909\n" + - "2015-01-22,0.2364\n" + - "2015-01-23,0.2283\n" + - "2015-01-24,0.2205\n" + - "2015-01-25,0.2649\n"; - - StringWriter writer = new StringWriter(); - habit.getScores().writeCSV(writer); - - assertThat(writer.toString(), equalTo(expectedCSV)); - } - private void toggleRepetitions(final int from, final int to) - { - DatabaseUtils.executeAsTransaction(() -> { - long today = DateUtils.getStartOfToday(); - for (int i = from; i < to; i++) - habit - .getRepetitions() - .toggleTimestamp(today - i * DateUtils.millisecondsInOneDay); - }); - } } diff --git a/app/src/main/java/org/isoron/uhabits/BaseComponent.java b/app/src/main/java/org/isoron/uhabits/BaseComponent.java index 2c6c550ea..069caae05 100644 --- a/app/src/main/java/org/isoron/uhabits/BaseComponent.java +++ b/app/src/main/java/org/isoron/uhabits/BaseComponent.java @@ -57,8 +57,6 @@ public interface BaseComponent void inject(ToggleRepetitionTask toggleRepetitionTask); - void inject(BaseDialogFragment baseDialogFragment); - void inject(HabitCardListCache habitCardListCache); void inject(HabitBroadcastReceiver habitBroadcastReceiver); @@ -100,4 +98,6 @@ public interface BaseComponent void inject(AbstractImporter abstractImporter); void inject(HabitsCSVExporter habitsCSVExporter); + + void inject(BaseDialogFragment baseDialogFragment); } diff --git a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java index 775b1b032..f7c712dfb 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java +++ b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java @@ -49,11 +49,11 @@ public class Checkmark */ public static final int UNCHECKED = 0; - final Habit habit; + private final Habit habit; - final long timestamp; + private final long timestamp; - final int value; + private final int value; public Checkmark(Habit habit, long timestamp, int value) { @@ -62,6 +62,11 @@ public class Checkmark this.value = value; } + public Habit getHabit() + { + return habit; + } + public long getTimestamp() { return timestamp; diff --git a/app/src/main/java/org/isoron/uhabits/models/HabitList.java b/app/src/main/java/org/isoron/uhabits/models/HabitList.java index 8c8fd8226..946aab86b 100644 --- a/app/src/main/java/org/isoron/uhabits/models/HabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/HabitList.java @@ -55,7 +55,7 @@ public abstract class HabitList * * @param habit the habit to be inserted */ - public abstract void add(Habit habit); + public abstract void add(@NonNull Habit habit); /** * Returns the total number of unarchived habits. @@ -87,6 +87,7 @@ public abstract class HabitList * @param id the id of the habit * @return the habit, or null if none exist */ + @Nullable public abstract Habit getById(long id); /** @@ -136,7 +137,7 @@ public abstract class HabitList * @param h the habit * @return the index of the habit, or -1 if not in the list */ - public abstract int indexOf(Habit h); + public abstract int indexOf(@NonNull Habit h); /** * Removes the given habit from the list. @@ -173,7 +174,7 @@ public abstract class HabitList * * @param habit the habit that has been modified. */ - public void update(Habit habit) + public void update(@NonNull Habit habit) { update(Collections.singletonList(habit)); } @@ -187,7 +188,7 @@ public abstract class HabitList * @param out the writer that will receive the result * @throws IOException if write operations fail */ - public void writeCSV(Writer out) throws IOException + public void writeCSV(@NonNull Writer out) throws IOException { String header[] = { "Position", diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index 051825805..aa0511c0a 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -189,9 +189,9 @@ public abstract class RepetitionList add(rep); } -// habit.getScores().invalidateNewerThan(timestamp); -// habit.getCheckmarks().invalidateNewerThan(timestamp); -// habit.getStreaks().invalidateNewerThan(timestamp); + habit.getScores().invalidateNewerThan(timestamp); + habit.getCheckmarks().invalidateNewerThan(timestamp); + habit.getStreaks().invalidateNewerThan(timestamp); return rep; } } diff --git a/app/src/main/java/org/isoron/uhabits/models/Score.java b/app/src/main/java/org/isoron/uhabits/models/Score.java index 5943950fe..2ad7b5aae 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Score.java +++ b/app/src/main/java/org/isoron/uhabits/models/Score.java @@ -35,12 +35,12 @@ public class Score * Timestamp of the day to which this score applies. Time of day should be * midnight (UTC). */ - private Long timestamp; + private final Long timestamp; /** * Value of the score. */ - private Integer value; + private final Integer value; /** * Maximum score value attainable by any habit. @@ -86,6 +86,11 @@ public class Score return score; } + public int compareNewer(Score other) + { + return Long.signum(this.getTimestamp() - other.getTimestamp()); + } + public Habit getHabit() { return habit; diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index 7b0749a54..ec4ae3d81 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -19,19 +19,17 @@ package org.isoron.uhabits.models; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.activeandroid.Cache; - import org.isoron.uhabits.utils.DateUtils; import java.io.IOException; import java.io.Writer; import java.text.SimpleDateFormat; -import java.util.Date; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -55,39 +53,7 @@ public abstract class ScoreList observable = new ModelObservable(); } - /** - * Returns the values of all the scores, from day of the first repetition - * until today, grouped in chunks of specified size. - *

- * If the group size is one, then the value of each score is returned - * individually. If the group is, for example, seven, then the days are - * grouped in groups of seven consecutive days. - *

- * The values are returned in an array of integers, with one entry for each - * group of days in the interval. This value corresponds to the average of - * the scores for the days inside the group. The first entry corresponds to - * the ending of the interval (that is, the most recent group of days). The - * last entry corresponds to the beginning of the interval. As usual, the - * time of the day for the timestamps should be midnight (UTC). The - * endpoints of the interval are included. - *

- * The values are returned in an integer array. There is one entry for each - * day inside the interval. The first entry corresponds to today, while the - * last entry corresponds to the day of the oldest repetition. - * - * @param divisor the size of the groups - * @return array of values, with one entry for each group of days - */ - @NonNull - public int[] getAllValues(long divisor) - { - Repetition oldestRep = habit.getRepetitions().getOldest(); - if (oldestRep == null) return new int[0]; - - long fromTimestamp = oldestRep.getTimestamp(); - long toTimestamp = DateUtils.getStartOfToday(); - return getValues(fromTimestamp, toTimestamp, divisor); - } + public abstract List getAll(); public ModelObservable getObservable() { @@ -112,6 +78,14 @@ public abstract class ScoreList */ public abstract int getValue(long timestamp); + public List groupBy(DateUtils.TruncateField field) + { + HashMap> groups = getGroupedValues(field); + List scores = groupsToAvgScores(groups); + Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1)); + return scores; + } + /** * Marks all scores that have timestamp equal to or newer than the given * timestamp as invalid. Any following getValue calls will trigger the @@ -124,29 +98,15 @@ public abstract class ScoreList public void writeCSV(Writer out) throws IOException { computeAll(); - SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); - String query = - "select timestamp, score from score where habit = ? order by timestamp"; - String params[] = {habit.getId().toString()}; - - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); - - if (!cursor.moveToFirst()) return; - - do + for (Score s : getAll()) { - String timestamp = dateFormat.format(new Date(cursor.getLong(0))); - String score = String.format("%.4f", - ((float) cursor.getInt(1)) / Score.MAX_VALUE); + String timestamp = dateFormat.format(s.getTimestamp()); + String score = + String.format("%.4f", ((float) s.getValue()) / Score.MAX_VALUE); out.write(String.format("%s,%s\n", timestamp, score)); - - } while (cursor.moveToNext()); - - cursor.close(); - out.close(); + } } protected abstract void add(List scores); @@ -175,7 +135,7 @@ public abstract class ScoreList long newestTimestamp = 0; Score newest = getNewestComputed(); - if(newest != null) + if (newest != null) { newestValue = newest.getValue(); newestTimestamp = newest.getTimestamp(); @@ -218,10 +178,29 @@ public abstract class ScoreList * @param timestamp the timestamp for the day * @return the score for the day */ + @Nullable protected abstract Score get(long timestamp); + @NonNull + private HashMap> getGroupedValues(DateUtils.TruncateField field) + { + HashMap> groups = new HashMap<>(); + + for (Score s : getAll()) + { + long groupTimestamp = DateUtils.truncate(field, s.getTimestamp()); + + if (!groups.containsKey(groupTimestamp)) + groups.put(groupTimestamp, new ArrayList<>()); + + groups.get(groupTimestamp).add((long) s.getValue()); + } + + return groups; + } + /** - * Returns the most recent score that was already computed. + * Returns the most recent score that has already been computed. *

* If no score has been computed yet, returns null. * @@ -230,13 +209,23 @@ public abstract class ScoreList @Nullable protected abstract Score getNewestComputed(); - /** - * Same as getAllValues(long), but using a specified interval. - * - * @param from beginning of the interval (included) - * @param to end of the interval (included) - * @param divisor size of the groups - * @return array of values, with one entry for each group of days - */ - protected abstract int[] getValues(long from, long to, long divisor); + + @NonNull + private List groupsToAvgScores(HashMap> groups) + { + List scores = new LinkedList<>(); + + for (Long timestamp : groups.keySet()) + { + long meanValue = 0L; + ArrayList groupValues = groups.get(timestamp); + + for (Long v : groupValues) meanValue += v; + meanValue /= groupValues.size(); + + scores.add(new Score(habit, timestamp, (int) meanValue)); + } + + return scores; + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java index 19fc4718c..870a4f16e 100644 --- a/app/src/main/java/org/isoron/uhabits/models/StreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java @@ -48,6 +48,7 @@ public abstract class StreakList public abstract List getAll(); + @NonNull public List getBest(int limit) { List streaks = getAll(); @@ -57,8 +58,10 @@ public abstract class StreakList return streaks; } + @Nullable public abstract Streak getNewestComputed(); + @NonNull public ModelObservable getObservable() { return observable; @@ -89,7 +92,7 @@ public abstract class StreakList * @return the list of streaks. */ @NonNull - protected List checkmarksToStreaks(Long beginning, int[] checks) + protected List checkmarksToStreaks(long beginning, int[] checks) { ArrayList transitions = getTransitions(beginning, checks); @@ -130,7 +133,7 @@ public abstract class StreakList * @return the list of transitions */ @NonNull - protected ArrayList getTransitions(Long beginning, int[] checks) + protected ArrayList getTransitions(long beginning, int[] checks) { long day = DateUtils.millisecondsInOneDay; long current = beginning; @@ -152,7 +155,7 @@ public abstract class StreakList return list; } - protected abstract void insert(List streaks); + protected abstract void insert(@NonNull List streaks); protected abstract void removeNewestComputed(); } diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java index 389d7f59e..baef4ae40 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java @@ -50,7 +50,7 @@ public class MemoryModelFactory implements ModelFactory @Override public ScoreList buildScoreList(Habit habit) { - return null; + return new MemoryScoreList(habit); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java new file mode 100644 index 000000000..4dac68e3a --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java @@ -0,0 +1,96 @@ +/* + * 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.memory; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.models.ScoreList; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class MemoryScoreList extends ScoreList +{ + List list; + + public MemoryScoreList(Habit habit) + { + super(habit); + list = new LinkedList<>(); + } + + @Override + public int getValue(long timestamp) + { + Score s = get(timestamp); + if (s != null) return s.getValue(); + return 0; + } + + @Override + public void invalidateNewerThan(long timestamp) + { + List discard = new LinkedList<>(); + + for (Score s : list) + if (s.getTimestamp() >= timestamp) discard.add(s); + + list.removeAll(discard); + } + + @Override + @NonNull + public List getAll() + { + computeAll(); + return new LinkedList<>(list); + } + + @Override + protected void add(List scores) + { + list.addAll(scores); + Collections.sort(list, + (s1, s2) -> Long.signum(s2.getTimestamp() - s1.getTimestamp())); + } + + @Override + @Nullable + protected Score get(long timestamp) + { + computeAll(); + for (Score s : list) + if (s.getTimestamp() == timestamp) return s; + + return null; + } + + @Nullable + @Override + protected Score getNewestComputed() + { + if(list.isEmpty()) return null; + return list.get(0); + } +} 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 f3647e4d6..ba059e938 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 @@ -106,6 +106,7 @@ public class SQLiteCheckmarkList extends CheckmarkList .limit(1) .executeSingle(); + if(record == null) return null; return record.toCheckmark(); } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java index 2a8f33b91..34e97104e 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java @@ -54,7 +54,7 @@ public class SQLiteHabitList extends HabitList } @Override - public void add(Habit habit) + public void add(@NonNull Habit habit) { if(cache.containsValue(habit)) throw new RuntimeException("habit already in cache"); @@ -132,7 +132,7 @@ public class SQLiteHabitList extends HabitList } @Override - public int indexOf(Habit h) + public int indexOf(@NonNull Habit h) { HabitRecord record = HabitRecord.get(h.getId()); if (record == null) return -1; diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java index e8d1603f3..63b68922c 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java @@ -19,7 +19,6 @@ package org.isoron.uhabits.models.sqlite; -import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; import android.support.annotation.NonNull; @@ -32,11 +31,10 @@ import com.activeandroid.query.Select; import com.activeandroid.util.SQLiteUtils; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Repetition; import org.isoron.uhabits.models.Score; import org.isoron.uhabits.models.ScoreList; -import org.isoron.uhabits.utils.DateUtils; +import java.util.LinkedList; import java.util.List; /** @@ -73,11 +71,25 @@ public class SQLiteScoreList extends ScoreList .execute(); } + @Override + @NonNull + public List getAll() + { + List records = select().execute(); + List scores = new LinkedList<>(); + + for(ScoreRecord rec : records) + scores.add(rec.toScore()); + + return scores; + } + @Nullable @Override protected Score getNewestComputed() { ScoreRecord record = select().limit(1).executeSingle(); + if(record == null) return null; return record.toScore(); } @@ -85,55 +97,15 @@ public class SQLiteScoreList extends ScoreList @Nullable protected Score get(long timestamp) { - Repetition oldestRep = habit.getRepetitions().getOldest(); - if (oldestRep == null) return null; - compute(oldestRep.getTimestamp(), timestamp); + computeAll(); ScoreRecord record = select().where("timestamp = ?", timestamp).executeSingle(); + if(record == null) return null; return record.toScore(); } - @Override - @NonNull - protected int[] getValues(long from, long to, long divisor) - { - compute(from, to); - - divisor *= DateUtils.millisecondsInOneDay; - Long offset = to + divisor; - - String query = - "select ((timestamp - ?) / ?) as time, avg(score) from Score " + - "where habit = ? and timestamp >= ? and timestamp <= ? " + - "group by time order by time desc"; - - String params[] = { - offset.toString(), - Long.toString(divisor), - habit.getId().toString(), - Long.toString(from), - Long.toString(to) - }; - - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); - - if (!cursor.moveToFirst()) return new int[0]; - - int k = 0; - int[] scores = new int[cursor.getCount()]; - - do - { - scores[k++] = (int) cursor.getFloat(1); - } while (cursor.moveToNext()); - - cursor.close(); - return scores; - } - @Override protected void add(List scores) { diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java index e44daa300..410a1288e 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java @@ -19,6 +19,9 @@ package org.isoron.uhabits.models.sqlite; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + import com.activeandroid.query.Delete; import com.activeandroid.query.Select; @@ -57,8 +60,9 @@ public class SQLiteStreakList extends StreakList @Override public Streak getNewestComputed() { - rebuild(); - return getNewestRecord().toStreak(); + StreakRecord newestRecord = getNewestRecord(); + if(newestRecord == null) return null; + return newestRecord.toStreak(); } @Override @@ -73,6 +77,7 @@ public class SQLiteStreakList extends StreakList observable.notifyListeners(); } + @Nullable private StreakRecord getNewestRecord() { return new Select() @@ -84,7 +89,7 @@ public class SQLiteStreakList extends StreakList } @Override - protected void insert(List streaks) + protected void insert(@NonNull List streaks) { DatabaseUtils.executeAsTransaction(() -> { for (Streak streak : streaks) @@ -96,6 +101,7 @@ public class SQLiteStreakList extends StreakList }); } + @NonNull private List recordsToStreaks(List records) { LinkedList streaks = new LinkedList<>(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java index aecb2a5e4..07d50342b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java @@ -36,6 +36,7 @@ import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.Preferences; @@ -50,17 +51,23 @@ import butterknife.OnItemSelected; public abstract class BaseDialogFragment extends AppCompatDialogFragment { + @Nullable protected Habit originalHabit; + @Nullable protected Habit modifiedHabit; + @Nullable protected BaseDialogHelper helper; @Inject - Preferences prefs; + protected Preferences prefs; @Inject - CommandRunner commandRunner; + protected CommandRunner commandRunner; + + @Inject + protected HabitList habitList; @Override public View onCreateView(LayoutInflater inflater, diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java index 2541100d6..d4e9beb76 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/EditHabitDialogFragment.java @@ -21,20 +21,13 @@ package org.isoron.uhabits.ui.habits.edit; import android.os.Bundle; -import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.EditHabitCommand; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; - -import javax.inject.Inject; public class EditHabitDialogFragment extends BaseDialogFragment { - @Inject - HabitList habitList; - public static EditHabitDialogFragment newInstance(long habitId) { EditHabitDialogFragment frag = new EditHabitDialogFragment(); @@ -53,8 +46,6 @@ public class EditHabitDialogFragment extends BaseDialogFragment @Override protected void initializeHabits() { - HabitsApplication.getComponent().inject(this); - Long habitId = (Long) getArguments().get("habitId"); if (habitId == null) throw new IllegalArgumentException("habitId must be specified"); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java index 0b7cf2611..377edb83a 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java @@ -27,8 +27,10 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; +import android.util.Log; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; @@ -42,6 +44,8 @@ import org.isoron.uhabits.utils.InterfaceUtils; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.LinkedList; +import java.util.List; import java.util.Random; public class HabitScoreView extends ScrollableDataView @@ -86,7 +90,7 @@ public class HabitScoreView extends ScrollableDataView private int gridColor; @Nullable - private int[] scores; + private List scores; private int primaryColor; @@ -134,7 +138,11 @@ public class HabitScoreView extends ScrollableDataView else { if (habit == null) return; - scores = habit.getScores().getAllValues(bucketSize); + if (bucketSize == 1) + scores = habit.getScores().getAll(); + else + scores = habit.getScores().groupBy(getTruncateField()); + createColors(); } @@ -285,14 +293,20 @@ public class HabitScoreView extends ScrollableDataView private void generateRandomData() { Random random = new Random(); - scores = new int[100]; - scores[0] = Score.MAX_VALUE / 2; + scores = new LinkedList<>(); + + int previous = Score.MAX_VALUE / 2; + long timestamp = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; for (int i = 1; i < 100; i++) { int step = Score.MAX_VALUE / 10; - scores[i] = scores[i - 1] + random.nextInt(step * 2) - step; - scores[i] = Math.max(0, Math.min(Score.MAX_VALUE, scores[i])); + int current = previous + random.nextInt(step * 2) - step; + current = Math.max(0, Math.min(Score.MAX_VALUE, current)); + scores.add(new Score(habit, timestamp, current)); + previous = current; + timestamp -= day; } } @@ -326,6 +340,38 @@ public class HabitScoreView extends ScrollableDataView return maxMonthWidth; } + @NonNull + private DateUtils.TruncateField getTruncateField() + { + DateUtils.TruncateField field; + + switch (bucketSize) + { + case 7: + field = DateUtils.TruncateField.WEEK_NUMBER; + break; + + case 365: + field = DateUtils.TruncateField.YEAR; + break; + + case 92: + field = DateUtils.TruncateField.QUARTER; + break; + + default: + Log.e("HabitScoreView", + String.format("Unknown bucket size: %d", bucketSize)); + // continue to case 31 + + case 31: + field = DateUtils.TruncateField.MONTH; + break; + } + + return field; + } + private void init() { createPaints(); @@ -413,7 +459,7 @@ public class HabitScoreView extends ScrollableDataView { int score = 0; int offset = nColumns - k - 1 + getDataOffset(); - if (offset < scores.length) score = scores[offset]; + if (offset < scores.size()) score = scores.get(offset).getValue(); double relativeScore = ((double) score) / Score.MAX_VALUE; int height = (int) (columnHeight * relativeScore); diff --git a/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java b/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java index a93a38c8b..c28128387 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/DateUtils.java @@ -33,58 +33,23 @@ import java.util.TimeZone; public abstract class DateUtils { - public static long millisecondsInOneDay = 24 * 60 * 60 * 1000; public static int ALL_WEEK_DAYS = 127; private static Long fixedLocalTime = null; - public static long getLocalTime() - { - if(fixedLocalTime != null) return fixedLocalTime; - - TimeZone tz = TimeZone.getDefault(); - long now = new Date().getTime(); - return now + tz.getOffset(now); - } - - public static void setFixedLocalTime(Long timestamp) - { - fixedLocalTime = timestamp; - } - - public static long toLocalTime(long timestamp) - { - TimeZone tz = TimeZone.getDefault(); - long now = new Date(timestamp).getTime(); - return now + tz.getOffset(now); - } - - public static long getStartOfDay(long timestamp) - { - return (timestamp / millisecondsInOneDay) * millisecondsInOneDay; - } - - public static GregorianCalendar getStartOfTodayCalendar() - { - return getCalendar(getStartOfToday()); - } - - public static GregorianCalendar getCalendar(long timestamp) - { - GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT")); - day.setTimeInMillis(timestamp); - return day; - } + /** + * Number of milliseconds in one day. + */ + public static long millisecondsInOneDay = 24 * 60 * 60 * 1000; - public static int getWeekday(long timestamp) + public static String formatHeaderDate(GregorianCalendar day) { - GregorianCalendar day = getCalendar(timestamp); - return day.get(GregorianCalendar.DAY_OF_WEEK) % 7; - } + String dayOfMonth = + Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH)); + String dayOfWeek = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, + GregorianCalendar.SHORT, Locale.getDefault()); - public static long getStartOfToday() - { - return getStartOfDay(DateUtils.getLocalTime()); + return dayOfWeek + "\n" + dayOfMonth; } public static String formatTime(Context context, int hours, int minutes) @@ -98,74 +63,77 @@ public abstract class DateUtils return df.format(date); } - public static SimpleDateFormat getDateFormat(String skeleton) + public static String formatWeekdayList(Context context, boolean weekday[]) { - String pattern; - Locale locale = Locale.getDefault(); + String shortDayNames[] = getShortDayNames(); + String longDayNames[] = getLongDayNames(); + StringBuilder buffer = new StringBuilder(); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) - pattern = DateFormat.getBestDateTimePattern(locale, skeleton); - else - pattern = skeleton; + int count = 0; + int first = 0; + boolean isFirst = true; + for (int i = 0; i < 7; i++) + { + if (weekday[i]) + { + if (isFirst) first = i; + else buffer.append(", "); - SimpleDateFormat format = new SimpleDateFormat(pattern, locale); - format.setTimeZone(TimeZone.getTimeZone("UTC")); + buffer.append(shortDayNames[i]); + isFirst = false; + count++; + } + } - return format; + if (count == 1) return longDayNames[first]; + if (count == 2 && weekday[0] && weekday[1]) + return context.getString(R.string.weekends); + if (count == 5 && !weekday[0] && !weekday[1]) + return context.getString(R.string.any_weekday); + if (count == 7) return context.getString(R.string.any_day); + return buffer.toString(); } - public static SimpleDateFormat getCSVDateFormat() + public static SimpleDateFormat getBackupDateFormat() { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + SimpleDateFormat dateFormat = + new SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormat; } - public static SimpleDateFormat getBackupDateFormat() + public static SimpleDateFormat getCSVDateFormat() { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US); + SimpleDateFormat dateFormat = + new SimpleDateFormat("yyyy-MM-dd", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return dateFormat; } - public static String formatHeaderDate(GregorianCalendar day) - { - String dayOfMonth = Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH)); - String dayOfWeek = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, - GregorianCalendar.SHORT, Locale.getDefault()); - - return dayOfWeek + "\n" + dayOfMonth; - } - - public static int differenceInDays(Date from, Date to) + public static GregorianCalendar getCalendar(long timestamp) { - long milliseconds = getStartOfDay(to.getTime()) - getStartOfDay(from.getTime()); - return (int) (milliseconds / millisecondsInOneDay); + GregorianCalendar day = + new GregorianCalendar(TimeZone.getTimeZone("GMT")); + day.setTimeInMillis(timestamp); + return day; } - public static String[] getShortDayNames() + public static SimpleDateFormat getDateFormat(String skeleton) { - return getDayNames(GregorianCalendar.SHORT); - } + String pattern; + Locale locale = Locale.getDefault(); - public static String[] getLongDayNames() - { - return getDayNames(GregorianCalendar.LONG); - } + if (android.os.Build.VERSION.SDK_INT >= + android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) + pattern = DateFormat.getBestDateTimePattern(locale, skeleton); + else pattern = skeleton; + SimpleDateFormat format = new SimpleDateFormat(pattern, locale); + format.setTimeZone(TimeZone.getTimeZone("UTC")); - /** - * Throughout the code, it is assumed that the weekdays are numbered from 0 (Saturday) to 6 - * (Friday). In the Java Calendar they are numbered from 1 (Sunday) to 7 (Saturday). This - * function converts from Java to our internal representation. - * - * @return weekday number in the internal interpretation - */ - public static int javaWeekdayToLoopWeekday(int number) - { - return number % 7; + return format; } public static String[] getDayNames(int format) @@ -178,13 +146,22 @@ public abstract class DateUtils for (int i = 0; i < wdays.length; i++) { wdays[i] = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format, - Locale.getDefault()); + Locale.getDefault()); day.add(GregorianCalendar.DAY_OF_MONTH, 1); } return wdays; } + public static long getLocalTime() + { + if (fixedLocalTime != null) return fixedLocalTime; + + TimeZone tz = TimeZone.getDefault(); + long now = new Date().getTime(); + return now + tz.getOffset(now); + } + /** * @return array with weekday names starting according to locale settings, * e.g. [Mo,Di,Mi,Do,Fr,Sa,So] in Europe @@ -194,10 +171,12 @@ public abstract class DateUtils String[] days = new String[7]; Calendar calendar = new GregorianCalendar(); - calendar.set(GregorianCalendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek()); + calendar.set(GregorianCalendar.DAY_OF_WEEK, + calendar.getFirstDayOfWeek()); for (int i = 0; i < days.length; i++) { - days[i] = calendar.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format, + days[i] = + calendar.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format, Locale.getDefault()); calendar.add(GregorianCalendar.DAY_OF_MONTH, 1); } @@ -206,14 +185,15 @@ public abstract class DateUtils } /** - * @return array with week days numbers starting according to locale settings, - * e.g. [2,3,4,5,6,7,1] in Europe + * @return array with week days numbers starting according to locale + * settings, e.g. [2,3,4,5,6,7,1] in Europe */ public static Integer[] getLocaleWeekdayList() { Integer[] dayNumbers = new Integer[7]; Calendar calendar = new GregorianCalendar(); - calendar.set(GregorianCalendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek()); + calendar.set(GregorianCalendar.DAY_OF_WEEK, + calendar.getFirstDayOfWeek()); for (int i = 0; i < dayNumbers.length; i++) { dayNumbers[i] = calendar.get(GregorianCalendar.DAY_OF_WEEK); @@ -222,33 +202,48 @@ public abstract class DateUtils return dayNumbers; } - public static String formatWeekdayList(Context context, boolean weekday[]) + public static String[] getLongDayNames() { - String shortDayNames[] = getShortDayNames(); - String longDayNames[] = getLongDayNames(); - StringBuilder buffer = new StringBuilder(); + return getDayNames(GregorianCalendar.LONG); + } - int count = 0; - int first = 0; - boolean isFirst = true; - for(int i = 0; i < 7; i++) - { - if(weekday[i]) - { - if(isFirst) first = i; - else buffer.append(", "); + public static String[] getShortDayNames() + { + return getDayNames(GregorianCalendar.SHORT); + } - buffer.append(shortDayNames[i]); - isFirst = false; - count++; - } - } + public static long getStartOfDay(long timestamp) + { + return (timestamp / millisecondsInOneDay) * millisecondsInOneDay; + } - if(count == 1) return longDayNames[first]; - if(count == 2 && weekday[0] && weekday[1]) return context.getString(R.string.weekends); - if(count == 5 && !weekday[0] && !weekday[1]) return context.getString(R.string.any_weekday); - if(count == 7) return context.getString(R.string.any_day); - return buffer.toString(); + public static long getStartOfToday() + { + return getStartOfDay(DateUtils.getLocalTime()); + } + + public static GregorianCalendar getStartOfTodayCalendar() + { + return getCalendar(getStartOfToday()); + } + + public static int getWeekday(long timestamp) + { + GregorianCalendar day = getCalendar(timestamp); + return day.get(GregorianCalendar.DAY_OF_WEEK) % 7; + } + + /** + * Throughout the code, it is assumed that the weekdays are numbered from 0 + * (Saturday) to 6 (Friday). In the Java Calendar they are numbered from 1 + * (Sunday) to 7 (Saturday). This function converts from Java to our + * internal representation. + * + * @return weekday number in the internal interpretation + */ + public static int javaWeekdayToLoopWeekday(int number) + { + return number % 7; } public static Integer packWeekdayList(boolean weekday[]) @@ -256,26 +251,77 @@ public abstract class DateUtils int list = 0; int current = 1; - for(int i = 0; i < 7; i++) + for (int i = 0; i < 7; i++) { - if(weekday[i]) list |= current; + if (weekday[i]) list |= current; current = current << 1; } return list; } + public static void setFixedLocalTime(Long timestamp) + { + fixedLocalTime = timestamp; + } + + public static long toLocalTime(long timestamp) + { + TimeZone tz = TimeZone.getDefault(); + long now = new Date(timestamp).getTime(); + return now + tz.getOffset(now); + } + + public static Long truncate(TruncateField field, long timestamp) + { + GregorianCalendar cal = DateUtils.getCalendar(timestamp); + + switch (field) + { + case MONTH: + cal.set(Calendar.DAY_OF_MONTH, 1); + return cal.getTimeInMillis(); + + case WEEK_NUMBER: + int firstWeekday = cal.getFirstDayOfWeek(); + int weekday = cal.get(Calendar.DAY_OF_WEEK); + int delta = weekday - firstWeekday; + if (delta < 0) delta += 7; + cal.add(Calendar.DAY_OF_YEAR, -delta); + return cal.getTimeInMillis(); + + case QUARTER: + int quarter = cal.get(Calendar.MONTH) / 3; + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.set(Calendar.MONTH, quarter * 3); + return cal.getTimeInMillis(); + + case YEAR: + cal.set(Calendar.MONTH, Calendar.JANUARY); + cal.set(Calendar.DAY_OF_MONTH, 1); + return cal.getTimeInMillis(); + + default: + throw new IllegalArgumentException(); + } + } + public static boolean[] unpackWeekdayList(int list) { boolean[] weekday = new boolean[7]; int current = 1; - for(int i = 0; i < 7; i++) + for (int i = 0; i < 7; i++) { - if((list & current) != 0) weekday[i] = true; + if ((list & current) != 0) weekday[i] = true; current = current << 1; } return weekday; } + + public enum TruncateField + { + MONTH, WEEK_NUMBER, YEAR, QUARTER + } } diff --git a/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java b/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java new file mode 100644 index 000000000..9fdc2d48a --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java @@ -0,0 +1,195 @@ +/* + * 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; + +import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.utils.DateUtils; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.*; + +public class ScoreListTest extends BaseUnitTest +{ + private Habit habit; + + @Override + @Before + public void setUp() + { + super.setUp(); + habit = fixtures.createEmptyHabit(); + } + + @Test + public void test_getAll() + { + toggleRepetitions(0, 20); + + int expectedValues[] = { + 12629351, + 12266245, + 11883254, + 11479288, + 11053198, + 10603773, + 10129735, + 9629735, + 9102352, + 8546087, + 7959357, + 7340494, + 6687738, + 5999234, + 5273023, + 4507040, + 3699107, + 2846927, + 1948077, + 1000000 + }; + + int actualValues[] = new int[expectedValues.length]; + + int i = 0; + for (Score s : habit.getScores().getAll()) + actualValues[i++] = s.getValue(); + + assertThat(actualValues, equalTo(expectedValues)); + } + + @Test + public void test_getTodayValue() + { + toggleRepetitions(0, 20); + assertThat(habit.getScores().getTodayValue(), equalTo(12629351)); + } + + @Test + public void test_getValue() + { + toggleRepetitions(0, 20); + + int expectedValues[] = { + 12629351, + 12266245, + 11883254, + 11479288, + 11053198, + 10603773, + 10129735, + 9629735, + 9102352, + 8546087, + 7959357, + 7340494, + 6687738, + 5999234, + 5273023, + 4507040, + 3699107, + 2846927, + 1948077, + 1000000 + }; + + long current = DateUtils.getStartOfToday(); + for (int expectedValue : expectedValues) + { + assertThat(habit.getScores().getValue(current), + equalTo(expectedValue)); + current -= DateUtils.millisecondsInOneDay; + } + } + + @Test + public void test_groupBy() + { + Habit habit = fixtures.createLongHabit(); + List list = + habit.getScores().groupBy(DateUtils.TruncateField.MONTH); + + assertThat(list.size(), equalTo(5)); + assertThat(list.get(0).getValue(), equalTo(14634077)); + assertThat(list.get(1).getValue(), equalTo(12969133)); + assertThat(list.get(2).getValue(), equalTo(10595391)); + } + + @Test + public void test_invalidateNewerThan() + { + assertThat(habit.getScores().getTodayValue(), equalTo(0)); + + toggleRepetitions(0, 2); + assertThat(habit.getScores().getTodayValue(), equalTo(1948077)); + + habit.setFreqNum(1); + habit.setFreqDen(2); + habit.getScores().invalidateNewerThan(0); + + assertThat(habit.getScores().getTodayValue(), equalTo(1974654)); + } + + @Test + public void test_writeCSV() throws IOException + { + Habit habit = fixtures.createShortHabit(); + + String expectedCSV = "2015-01-25,0.2649\n" + + "2015-01-24,0.2205\n" + + "2015-01-23,0.2283\n" + + "2015-01-22,0.2364\n" + + "2015-01-21,0.1909\n" + + "2015-01-20,0.1439\n" + + "2015-01-19,0.0952\n" + + "2015-01-18,0.0986\n" + + "2015-01-17,0.1021\n" + + "2015-01-16,0.0519\n"; + + StringWriter writer = new StringWriter(); + habit.getScores().writeCSV(writer); + + assertThat(writer.toString(), equalTo(expectedCSV)); + } + + private void log(List list) + { + SimpleDateFormat df = DateUtils.getCSVDateFormat(); + for (Score s : list) + log("%s %d", df.format(new Date(s.getTimestamp())), s.getValue()); + } + + private void toggleRepetitions(final int from, final int to) + { + RepetitionList reps = habit.getRepetitions(); + long today = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; + + for (int i = from; i < to; i++) + reps.toggleTimestamp(today - i * day); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java b/app/src/test/java/org/isoron/uhabits/models/ScoreTest.java similarity index 71% rename from app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java rename to app/src/test/java/org/isoron/uhabits/models/ScoreTest.java index bc0a31c31..34b400150 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/ScoreTest.java @@ -17,24 +17,16 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.models; +package org.isoron.uhabits.models; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; - -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.BaseUnitTest; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ScoreTest extends BaseAndroidTest +public class ScoreTest extends BaseUnitTest { @Override @Before @@ -50,20 +42,22 @@ public class ScoreTest extends BaseAndroidTest assertThat(Score.compute(1, 0, checkmark), equalTo(0)); assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387)); assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775)); - assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(18259478)); + assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), + equalTo(18259478)); checkmark = Checkmark.CHECKED_IMPLICITLY; assertThat(Score.compute(1, 0, checkmark), equalTo(0)); assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387)); assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775)); - assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(18259478)); + assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), + equalTo(18259478)); checkmark = Checkmark.CHECKED_EXPLICITLY; assertThat(Score.compute(1, 0, checkmark), equalTo(1000000)); assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387)); assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775)); - assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo( - Score.MAX_VALUE)); + assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), + equalTo(Score.MAX_VALUE)); } @Test @@ -71,15 +65,19 @@ public class ScoreTest extends BaseAndroidTest { int checkmark = Checkmark.CHECKED_EXPLICITLY; assertThat(Score.compute(1 / 3.0, 0, checkmark), equalTo(1000000)); - assertThat(Score.compute(1 / 3.0, 5000000, checkmark), equalTo(5916180)); - assertThat(Score.compute(1 / 3.0, 10000000, checkmark), equalTo(10832360)); - assertThat(Score.compute(1 / 3.0, Score.MAX_VALUE, checkmark), equalTo( - Score.MAX_VALUE)); + assertThat(Score.compute(1 / 3.0, 5000000, checkmark), + equalTo(5916180)); + assertThat(Score.compute(1 / 3.0, 10000000, checkmark), + equalTo(10832360)); + assertThat(Score.compute(1 / 3.0, Score.MAX_VALUE, checkmark), + equalTo(Score.MAX_VALUE)); assertThat(Score.compute(1 / 7.0, 0, checkmark), equalTo(1000000)); - assertThat(Score.compute(1 / 7.0, 5000000, checkmark), equalTo(5964398)); - assertThat(Score.compute(1 / 7.0, 10000000, checkmark), equalTo(10928796)); - assertThat(Score.compute(1 / 7.0, Score.MAX_VALUE, checkmark), equalTo( - Score.MAX_VALUE)); + assertThat(Score.compute(1 / 7.0, 5000000, checkmark), + equalTo(5964398)); + assertThat(Score.compute(1 / 7.0, 10000000, checkmark), + equalTo(10928796)); + assertThat(Score.compute(1 / 7.0, Score.MAX_VALUE, checkmark), + equalTo(Score.MAX_VALUE)); } } diff --git a/app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java b/app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java new file mode 100644 index 000000000..b3b26ecbc --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java @@ -0,0 +1,146 @@ +/* + * 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.utils; + +import org.isoron.uhabits.BaseUnitTest; +import org.junit.Test; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.*; + +public class DateUtilsTest extends BaseUnitTest +{ + @Test + public void testTruncate_dayOfWeek() + { + DateUtils.TruncateField field = DateUtils.TruncateField.WEEK_NUMBER; + + long expected = timestamp(2015, Calendar.JANUARY, 11); + long t0 = timestamp(2015, Calendar.JANUARY, 11); + long t1 = timestamp(2015, Calendar.JANUARY, 16); + long t2 = timestamp(2015, Calendar.JANUARY, 17); + + assertThat(DateUtils.truncate(field, t0), equalTo(expected)); + assertThat(DateUtils.truncate(field, t1), equalTo(expected)); + assertThat(DateUtils.truncate(field, t2), equalTo(expected)); + + expected = timestamp(2015, Calendar.JANUARY, 18); + t0 = timestamp(2015, Calendar.JANUARY, 18); + t1 = timestamp(2015, Calendar.JANUARY, 19); + t2 = timestamp(2015, Calendar.JANUARY, 24); + + assertThat(DateUtils.truncate(field, t0), equalTo(expected)); + assertThat(DateUtils.truncate(field, t1), equalTo(expected)); + assertThat(DateUtils.truncate(field, t2), equalTo(expected)); + } + + @Test + public void testTruncate_month() + { + long expected = timestamp(2016, Calendar.JUNE, 1); + long t0 = timestamp(2016, Calendar.JUNE, 1); + long t1 = timestamp(2016, Calendar.JUNE, 15); + long t2 = timestamp(2016, Calendar.JUNE, 20); + + DateUtils.TruncateField field = DateUtils.TruncateField.MONTH; + + assertThat(DateUtils.truncate(field, t0), equalTo(expected)); + assertThat(DateUtils.truncate(field, t1), equalTo(expected)); + assertThat(DateUtils.truncate(field, t2), equalTo(expected)); + + expected = timestamp(2016, Calendar.DECEMBER, 1); + t0 = timestamp(2016, Calendar.DECEMBER, 1); + t1 = timestamp(2016, Calendar.DECEMBER, 15); + t2 = timestamp(2016, Calendar.DECEMBER, 31); + + assertThat(DateUtils.truncate(field, t0), equalTo(expected)); + assertThat(DateUtils.truncate(field, t1), equalTo(expected)); + assertThat(DateUtils.truncate(field, t2), equalTo(expected)); + } + + @Test + public void testTruncate_quarter() + { + DateUtils.TruncateField field = DateUtils.TruncateField.QUARTER; + + long expected = timestamp(2016, Calendar.JANUARY, 1); + long t0 = timestamp(2016, Calendar.JANUARY, 20); + long t1 = timestamp(2016, Calendar.FEBRUARY, 15); + long t2 = timestamp(2016, Calendar.MARCH, 30); + + assertThat(DateUtils.truncate(field, t0), equalTo(expected)); + assertThat(DateUtils.truncate(field, t1), equalTo(expected)); + assertThat(DateUtils.truncate(field, t2), equalTo(expected)); + + expected = timestamp(2016, Calendar.APRIL, 1); + t0 = timestamp(2016, Calendar.APRIL, 1); + t1 = timestamp(2016, Calendar.MAY, 30); + t2 = timestamp(2016, Calendar.JUNE, 20); + + assertThat(DateUtils.truncate(field, t0), equalTo(expected)); + assertThat(DateUtils.truncate(field, t1), equalTo(expected)); + assertThat(DateUtils.truncate(field, t2), equalTo(expected)); + } + + @Test + public void testTruncate_year() + { + DateUtils.TruncateField field = DateUtils.TruncateField.YEAR; + + long expected = timestamp(2016, Calendar.JANUARY, 1); + long t0 = timestamp(2016, Calendar.JANUARY, 1); + long t1 = timestamp(2016, Calendar.FEBRUARY, 25); + long t2 = timestamp(2016, Calendar.DECEMBER, 31); + + assertThat(DateUtils.truncate(field, t0), equalTo(expected)); + assertThat(DateUtils.truncate(field, t1), equalTo(expected)); + assertThat(DateUtils.truncate(field, t2), equalTo(expected)); + + expected = timestamp(2017, Calendar.JANUARY, 1); + t0 = timestamp(2017, Calendar.JANUARY, 1); + t1 = timestamp(2017, Calendar.MAY, 30); + t2 = timestamp(2017, Calendar.DECEMBER, 31); + + assertThat(DateUtils.truncate(field, t0), equalTo(expected)); + assertThat(DateUtils.truncate(field, t1), equalTo(expected)); + assertThat(DateUtils.truncate(field, t2), equalTo(expected)); + } + + private void log(long timestamp) + { + DateFormat df = SimpleDateFormat.getDateTimeInstance(); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + log("%s", df.format(new Date(timestamp))); + } + + public long timestamp(int year, int month, int day) + { + GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); + cal.set(year, month, day); + return cal.getTimeInMillis(); + } +} From 9a447742841de02f9ed3b9d4a2c562801e680244 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 12 Jun 2016 07:55:47 -0400 Subject: [PATCH 018/184] Add instrumented unit tests for SQLite lists --- app/build.gradle | 3 +- .../org/isoron/uhabits/BaseAndroidTest.java | 4 +- .../sqlite/SQLiteCheckmarkListTest.java | 117 +++++++++ .../models/sqlite/SQLiteHabitListTest.java | 229 ++++++++++++++++++ .../sqlite/SQLiteRepetitionListTest.java | 145 +++++++++++ .../models/sqlite/SQLiteScoreListTest.java | 126 ++++++++++ .../uhabits/ui/MainActivityActions.java | 2 +- .../java/org/isoron/uhabits/ui/MainTest.java | 2 +- .../commands/ArchiveHabitsCommandTest.java | 3 +- .../commands/ChangeHabitColorCommandTest.java | 3 +- .../unit/commands/CreateHabitCommandTest.java | 3 +- .../commands/DeleteHabitsCommandTest.java | 30 ++- .../unit/commands/EditHabitCommandTest.java | 2 +- .../commands/ToggleRepetitionCommandTest.java | 2 +- .../commands/UnarchiveHabitsCommandTest.java | 2 +- .../unit/io/HabitsCSVExporterTest.java | 6 +- .../isoron/uhabits/unit/io/ImportTest.java | 2 +- .../uhabits/unit/models/ScoreListTest.java | 33 --- .../uhabits/unit/tasks/ExportCSVTaskTest.java | 29 +-- .../unit/views/CheckmarkWidgetViewTest.java | 2 +- .../unit/views/HabitFrequencyViewTest.java | 4 +- .../unit/views/HabitHistoryViewTest.java | 4 +- .../unit/views/HabitScoreViewTest.java | 4 +- .../unit/views/HabitStreakViewTest.java | 4 +- .../org/isoron/uhabits/models/Checkmark.java | 7 +- .../isoron/uhabits/models/CheckmarkList.java | 101 +++++--- .../java/org/isoron/uhabits/models/Habit.java | 27 ++- .../org/isoron/uhabits/models/HabitList.java | 32 +-- .../isoron/uhabits/models/ModelFactory.java | 10 +- .../uhabits/models/ModelObservable.java | 3 +- .../org/isoron/uhabits/models/Repetition.java | 4 +- .../isoron/uhabits/models/RepetitionList.java | 20 +- .../java/org/isoron/uhabits/models/Score.java | 12 +- .../org/isoron/uhabits/models/ScoreList.java | 84 ++++--- .../org/isoron/uhabits/models/Streak.java | 4 +- .../org/isoron/uhabits/models/StreakList.java | 14 +- .../models/memory/MemoryCheckmarkList.java | 48 ++-- .../models/memory/MemoryHabitList.java | 37 +-- .../models/memory/MemoryModelFactory.java | 10 +- .../models/memory/MemoryRepetitionList.java | 12 +- .../models/memory/MemoryScoreList.java | 41 ++-- .../models/memory/MemoryStreakList.java | 23 +- .../models/sqlite/SQLModelFactory.java | 12 +- .../models/sqlite/SQLiteCheckmarkList.java | 145 +++++------ .../models/sqlite/SQLiteHabitList.java | 37 +-- .../models/sqlite/SQLiteRepetitionList.java | 87 +++---- .../models/sqlite/SQLiteScoreList.java | 94 +++---- .../models/sqlite/SQLiteStreakList.java | 57 ++--- .../sqlite/{ => records}/CheckmarkRecord.java | 11 +- .../sqlite/{ => records}/HabitRecord.java | 22 +- .../{ => records}/RepetitionRecord.java | 11 +- .../sqlite/{ => records}/ScoreRecord.java | 11 +- .../sqlite/{ => records}/StreakRecord.java | 11 +- .../ui/habits/show/views/HabitScoreView.java | 4 +- .../isoron/uhabits/utils/DatabaseUtils.java | 30 +-- .../widgets/CheckmarkWidgetProvider.java | 36 +-- .../uhabits/models/CheckmarkListTest.java | 1 + .../isoron/uhabits/models/HabitListTest.java | 27 +-- .../uhabits/models/RepetitionListTest.java | 3 +- .../isoron/uhabits/models/ScoreListTest.java | 2 +- .../isoron/uhabits/models/StreakListTest.java | 11 +- 61 files changed, 1194 insertions(+), 668 deletions(-) create mode 100644 app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java create mode 100644 app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java create mode 100644 app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java create mode 100644 app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java delete mode 100644 app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java rename app/src/main/java/org/isoron/uhabits/models/sqlite/{ => records}/CheckmarkRecord.java (87%) rename app/src/main/java/org/isoron/uhabits/models/sqlite/{ => records}/HabitRecord.java (89%) rename app/src/main/java/org/isoron/uhabits/models/sqlite/{ => records}/RepetitionRecord.java (86%) rename app/src/main/java/org/isoron/uhabits/models/sqlite/{ => records}/ScoreRecord.java (87%) rename app/src/main/java/org/isoron/uhabits/models/sqlite/{ => records}/StreakRecord.java (87%) diff --git a/app/build.gradle b/app/build.gradle index 93b68ca48..b7e988bb1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,6 +15,7 @@ android { buildConfigField "String", "databaseFilename", "\"uhabits.db\"" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArgument "size", "medium" } buildTypes { @@ -40,7 +41,7 @@ android { unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" - outputs.upToDateWhen {false} + outputs.upToDateWhen { false } showStandardStreams = true } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java index 8be833bc0..94278eb01 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java @@ -55,7 +55,7 @@ public class BaseAndroidTest protected AndroidTestComponent androidTestComponent; - protected HabitFixtures habitFixtures; + protected HabitFixtures fixtures; @Before public void setUp() @@ -76,7 +76,7 @@ public class BaseAndroidTest HabitsApplication.setComponent(androidTestComponent); androidTestComponent.inject(this); - habitFixtures = new HabitFixtures(habitList); + fixtures = new HabitFixtures(habitList); } protected void waitForAsyncTasks() diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java new file mode 100644 index 000000000..4ccb87016 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java @@ -0,0 +1,117 @@ +/* + * 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; + +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; + +import com.activeandroid.query.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; +import org.junit.runner.*; + +import java.util.*; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +@RunWith(AndroidJUnit4.class) +@MediumTest +public class SQLiteCheckmarkListTest extends BaseAndroidTest +{ + private Habit habit; + + private CheckmarkList checkmarks; + + private long today; + + private long day; + + @Override + public void setUp() + { + super.setUp(); + + habit = fixtures.createLongHabit(); + checkmarks = habit.getCheckmarks(); + checkmarks.getToday(); // compute checkmarks + + today = DateUtils.getStartOfToday(); + day = DateUtils.millisecondsInOneDay; + } + + @Test + public void testAdd() + { + checkmarks.invalidateNewerThan(0); + + List list = new LinkedList<>(); + list.add(new Checkmark(habit, 0, 0)); + list.add(new Checkmark(habit, 1, 1)); + list.add(new Checkmark(habit, 2, 2)); + + checkmarks.add(list); + + List records = getAllRecords(); + assertThat(records.size(), equalTo(3)); + assertThat(records.get(0).timestamp, equalTo(2L)); + } + + @Test + public void testGetByInterval() + { + long from = today - 10 * day; + long to = today - 3 * day; + + List list = checkmarks.getByInterval(from, to); + assertThat(list.size(), equalTo(8)); + + assertThat(list.get(0).getTimestamp(), equalTo(today - 3 * day)); + assertThat(list.get(3).getTimestamp(), equalTo(today - 6 * day)); + assertThat(list.get(7).getTimestamp(), equalTo(today - 10 * day)); + } + + @Test + public void testInvalidateNewerThan() + { + List records = getAllRecords(); + assertThat(records.size(), equalTo(121)); + + checkmarks.invalidateNewerThan(today - 20 * day); + + records = getAllRecords(); + assertThat(records.size(), equalTo(100)); + assertThat(records.get(0).timestamp, equalTo(today - 21 * day)); + } + + private List getAllRecords() + { + return new Select() + .from(CheckmarkRecord.class) + .where("habit = ?", habit.getId()) + .orderBy("timestamp desc") + .execute(); + } + +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java new file mode 100644 index 000000000..6752e483b --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java @@ -0,0 +1,229 @@ +/* + * 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; + +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; + +import com.activeandroid.query.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; +import org.junit.*; +import org.junit.rules.*; +import org.junit.runner.*; + +import java.util.*; + +import static junit.framework.Assert.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.core.IsEqual.*; + +@SuppressWarnings("JavaDoc") +@RunWith(AndroidJUnit4.class) +@MediumTest +public class SQLiteHabitListTest extends BaseAndroidTest +{ + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Override + public void setUp() + { + super.setUp(); + fixtures.purgeHabits(habitList); + + for (int i = 0; i < 10; i++) + { + Habit h = new Habit(); + h.setName("habit " + i); + h.setId((long) i); + if (i % 2 == 0) h.setArchived(1); + + HabitRecord record = new HabitRecord(); + record.copyFrom(h); + record.position = i; + record.save(i); + } + } + + @Test + public void testAdd_withDuplicate() + { + Habit habit = new Habit(); + habitList.add(habit); + exception.expect(IllegalArgumentException.class); + habitList.add(habit); + } + + @Test + public void testAdd_withId() + { + Habit habit = new Habit(); + habit.setName("Hello world with id"); + habit.setId(12300L); + + habitList.add(habit); + assertThat(habit.getId(), equalTo(12300L)); + + HabitRecord record = getRecord(12300L); + assertNotNull(record); + assertThat(record.name, equalTo(habit.getName())); + } + + @Test + public void testAdd_withoutId() + { + Habit habit = new Habit(); + habit.setName("Hello world"); + assertNull(habit.getId()); + + habitList.add(habit); + assertNotNull(habit.getId()); + + HabitRecord record = getRecord(habit.getId()); + assertNotNull(record); + assertThat(record.name, equalTo(habit.getName())); + } + + @Test + public void testCountActive() + { + assertThat(habitList.countActive(), equalTo(5)); + } + + @Test + public void testCountWithArchived() + { + assertThat(habitList.countWithArchived(), equalTo(10)); + } + + @Test + public void testGetAll_withArchived() + { + List habits = habitList.getAll(true); + assertThat(habits.size(), equalTo(10)); + assertThat(habits.get(3).getName(), equalTo("habit 3")); + } + + @Test + public void testGetAll_withoutArchived() + { + List habits = habitList.getAll(false); + assertThat(habits.size(), equalTo(5)); + assertThat(habits.get(3).getName(), equalTo("habit 7")); + + List another = habitList.getAll(false); + assertThat(habits, equalTo(another)); + } + + @Test + public void testGetById() + { + Habit h1 = habitList.getById(0); + assertNotNull(h1); + assertThat(h1.getName(), equalTo("habit 0")); + + Habit h2 = habitList.getById(0); + assertNotNull(h2); + assertThat(h1, equalTo(h2)); + } + + @Test + public void testGetById_withInvalid() + { + long invalidId = 9183792001L; + Habit h1 = habitList.getById(invalidId); + assertNull(h1); + } + + @Test + public void testGetByPosition() + { + Habit h = habitList.getByPosition(5); + assertNotNull(h); + assertThat(h.getName(), equalTo("habit 5")); + + h = habitList.getByPosition(5000); + assertNull(h); + } + + @Test + public void testIndexOf() + { + Habit h1 = habitList.getByPosition(5); + assertNotNull(h1); + assertThat(habitList.indexOf(h1), equalTo(5)); + + Habit h2 = new Habit(); + assertThat(habitList.indexOf(h2), equalTo(-1)); + + h2.setId(1000L); + assertThat(habitList.indexOf(h2), equalTo(-1)); + } + + @Test + public void test_reorder() + { + // Same as HabitListTest.java + // TODO: remove duplication + + int operations[][] = { + {5, 2}, {3, 7}, {4, 4}, {3, 2} + }; + + int expectedPosition[][] = { + {0, 1, 3, 4, 5, 2, 6, 7, 8, 9}, + {0, 1, 7, 3, 4, 2, 5, 6, 8, 9}, + {0, 1, 7, 3, 4, 2, 5, 6, 8, 9}, + {0, 1, 7, 2, 4, 3, 5, 6, 8, 9}, + }; + + for (int i = 0; i < operations.length; i++) + { + int from = operations[i][0]; + int to = operations[i][1]; + + Habit fromHabit = habitList.getByPosition(from); + Habit toHabit = habitList.getByPosition(to); + habitList.reorder(fromHabit, toHabit); + + int actualPositions[] = new int[10]; + + for (int j = 0; j < 10; j++) + { + Habit h = habitList.getById(j); + assertNotNull(h); + actualPositions[j] = habitList.indexOf(h); + } + + assertThat(actualPositions, equalTo(expectedPosition[i])); + } + } + + private HabitRecord getRecord(long id) + { + return new Select() + .from(HabitRecord.class) + .where("id = ?", id) + .executeSingle(); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java new file mode 100644 index 000000000..ee2a579d5 --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java @@ -0,0 +1,145 @@ +/* + * 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; + +import android.support.annotation.*; +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; + +import com.activeandroid.query.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; +import org.junit.runner.*; + +import java.util.*; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.IsNot.not; + +@RunWith(AndroidJUnit4.class) +@MediumTest +public class SQLiteRepetitionListTest extends BaseAndroidTest +{ + private Habit habit; + + private long today; + + private RepetitionList repetitions; + + private long day; + + @Override + public void setUp() + { + super.setUp(); + + habit = fixtures.createLongHabit(); + repetitions = habit.getRepetitions(); + today = DateUtils.getStartOfToday(); + day = DateUtils.millisecondsInOneDay; + } + + @Test + public void testAdd() + { + RepetitionRecord record = getByTimestamp(today + day); + assertThat(record, is(nullValue())); + + Repetition rep = new Repetition(habit, today + day); + habit.getRepetitions().add(rep); + + record = getByTimestamp(today + day); + assertThat(record, is(not(nullValue()))); + } + + @Test + public void testGetByInterval() + { + List reps = + repetitions.getByInterval(today - 10 * day, today); + + assertThat(reps.size(), equalTo(8)); + assertThat(reps.get(0).getTimestamp(), equalTo(today - 10 * day)); + assertThat(reps.get(4).getTimestamp(), equalTo(today - 5 * day)); + assertThat(reps.get(5).getTimestamp(), equalTo(today - 3 * day)); + } + + @Test + public void testGetByTimestamp() + { + Repetition rep = repetitions.getByTimestamp(today); + assertThat(rep, is(not(nullValue()))); + assertThat(rep.getHabit(), equalTo(habit)); + assertThat(rep.getTimestamp(), equalTo(today)); + + rep = repetitions.getByTimestamp(today - 2 * day); + assertThat(rep, is(nullValue())); + } + + @Test + public void testGetOldest() + { + Repetition rep = repetitions.getOldest(); + assertThat(rep, is(not(nullValue()))); + assertThat(rep.getHabit(), equalTo(habit)); + assertThat(rep.getTimestamp(), equalTo(today - 120 * day)); + } + + @Test + public void testGetOldest_withEmptyHabit() + { + Habit empty = fixtures.createEmptyHabit(); + Repetition rep = empty.getRepetitions().getOldest(); + assertThat(rep, is(nullValue())); + } + + @Test + public void testRemove() + { + RepetitionRecord record = getByTimestamp(today); + assertThat(record, is(not(nullValue()))); + + Repetition rep = record.toRepetition(); + repetitions.remove(rep); + + record = getByTimestamp(today); + assertThat(record, is(nullValue())); + } + + @Nullable + private RepetitionRecord getByTimestamp(long timestamp) + { + return selectByTimestamp(timestamp).executeSingle(); + } + + @NonNull + private From selectByTimestamp(long timestamp) + { + return new Select() + .from(RepetitionRecord.class) + .where("habit = ?", habit.getId()) + .and("timestamp = ?", timestamp); + } +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java new file mode 100644 index 000000000..8011b147f --- /dev/null +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java @@ -0,0 +1,126 @@ +/* + * 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; + +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; + +import com.activeandroid.query.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; +import org.junit.runner.*; + +import java.util.*; + +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +@SuppressWarnings("JavaDoc") +@RunWith(AndroidJUnit4.class) +@MediumTest +public class SQLiteScoreListTest extends BaseAndroidTest +{ + private Habit habit; + + private ScoreList scores; + + private long today; + + private long day; + + @Override + public void setUp() + { + super.setUp(); + + habit = fixtures.createLongHabit(); + scores = habit.getScores(); + + today = DateUtils.getStartOfToday(); + day = DateUtils.millisecondsInOneDay; + } + + @Test + public void testGetAll() + { + List list = scores.getAll(); + assertThat(list.size(), equalTo(121)); + assertThat(list.get(0).getTimestamp(), equalTo(today)); + assertThat(list.get(10).getTimestamp(), equalTo(today - 10 * day)); + } + + @Test + public void testInvalidateNewerThan() + { + scores.getTodayValue(); // force recompute + List records = getAllRecords(); + assertThat(records.size(), equalTo(121)); + + scores.invalidateNewerThan(today - 10 * day); + + records = getAllRecords(); + assertThat(records.size(), equalTo(110)); + assertThat(records.get(0).timestamp, equalTo(today - 11 * day)); + } + + @Test + public void testAdd() + { + new Delete().from(ScoreRecord.class).execute(); + + List list = new LinkedList<>(); + list.add(new Score(habit, today, 0)); + list.add(new Score(habit, today - day, 0)); + list.add(new Score(habit, today - 2 * day, 0)); + + scores.add(list); + + List records = getAllRecords(); + assertThat(records.size(), equalTo(3)); + assertThat(records.get(0).timestamp, equalTo(today)); + } + + @Test + public void testGetByTimestamp() + { + Score s = scores.getByTimestamp(today); + assertNotNull(s); + assertThat(s.getTimestamp(), equalTo(today)); + + s = scores.getByTimestamp(today - 200 * day); + assertNull(s); + } + + private List getAllRecords() + { + return new Select() + .from(ScoreRecord.class) + .where("habit = ?", habit.getId()) + .orderBy("timestamp desc") + .execute(); + } + +} diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java index 5b63b0b5b..c37e50308 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java @@ -23,7 +23,7 @@ import android.support.test.espresso.NoMatchingViewException; import android.support.test.espresso.contrib.RecyclerViewActions; import org.isoron.uhabits.R; -import org.isoron.uhabits.models.sqlite.HabitRecord; +import org.isoron.uhabits.models.sqlite.records.HabitRecord; import java.util.Collections; import java.util.LinkedList; diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java index 9fba71854..eba6b19db 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java @@ -30,7 +30,7 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; import org.isoron.uhabits.R; -import org.isoron.uhabits.models.sqlite.HabitRecord; +import org.isoron.uhabits.models.sqlite.records.HabitRecord; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.MainActivity; import org.junit.After; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java index 57a43817a..7c0b492f0 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java @@ -25,7 +25,6 @@ import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ArchiveHabitsCommand; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,7 +47,7 @@ public class ArchiveHabitsCommandTest extends BaseAndroidTest { super.setUp(); - habit = habitFixtures.createShortHabit(); + habit = fixtures.createShortHabit(); command = new ArchiveHabitsCommand(Collections.singletonList(habit)); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java index bfff4f81b..c41656c29 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java @@ -25,7 +25,6 @@ import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.commands.ChangeHabitColorCommand; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.unit.HabitFixtures; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,7 +50,7 @@ public class ChangeHabitColorCommandTest extends BaseAndroidTest for(int i = 0; i < 3; i ++) { - Habit habit = habitFixtures.createShortHabit(); + Habit habit = fixtures.createShortHabit(); habit.setColor(i + 1); habits.add(habit); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java index c5a9f39f2..3c3f9ebfb 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java @@ -23,7 +23,6 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.commands.CreateHabitCommand; import org.isoron.uhabits.models.Habit; import org.junit.Before; @@ -54,7 +53,7 @@ public class CreateHabitCommandTest extends BaseAndroidTest model.setName("New habit"); command = new CreateHabitCommand(model); - habitFixtures.purgeHabits(habitList); + fixtures.purgeHabits(habitList); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java index 983448f41..565a5fdd9 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java @@ -19,22 +19,20 @@ package org.isoron.uhabits.unit.commands; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.commands.DeleteHabitsCommand; -import org.isoron.uhabits.models.Habit; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; +import org.isoron.uhabits.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.junit.*; +import org.junit.rules.*; +import org.junit.runner.*; -import java.util.LinkedList; +import java.util.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; @RunWith(AndroidJUnit4.class) @SmallTest @@ -53,18 +51,18 @@ public class DeleteHabitsCommandTest extends BaseAndroidTest { super.setUp(); - habitFixtures.purgeHabits(habitList); + fixtures.purgeHabits(habitList); habits = new LinkedList<>(); // Habits that should be deleted for (int i = 0; i < 3; i++) { - Habit habit = habitFixtures.createShortHabit(); + Habit habit = fixtures.createShortHabit(); habits.add(habit); } // Extra habit that should not be deleted - Habit extraHabit = habitFixtures.createShortHabit(); + Habit extraHabit = fixtures.createShortHabit(); extraHabit.setName("extra"); command = new DeleteHabitsCommand(habits); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java index 46f077481..a196c064d 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java @@ -50,7 +50,7 @@ public class EditHabitCommandTest extends BaseAndroidTest { super.setUp(); - habit = habitFixtures.createShortHabit(); + habit = fixtures.createShortHabit(); habit.setName("original"); habit.setFreqDen(1); habit.setFreqNum(1); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java index e14c35ad9..934d8e654 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java @@ -47,7 +47,7 @@ public class ToggleRepetitionCommandTest extends BaseAndroidTest { super.setUp(); - habit = habitFixtures.createShortHabit(); + habit = fixtures.createShortHabit(); today = DateUtils.getStartOfToday(); command = new ToggleRepetitionCommand(habit, today); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java index 07cc11104..d9235599b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java @@ -47,7 +47,7 @@ public class UnarchiveHabitsCommandTest extends BaseAndroidTest { super.setUp(); - habit = habitFixtures.createShortHabit(); + habit = fixtures.createShortHabit(); habit.setArchived(1); habitList.update(habit); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java index a965a7eb9..d4e35f1e5 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java @@ -53,9 +53,9 @@ public class HabitsCSVExporterTest extends BaseAndroidTest { super.setUp(); - habitFixtures.purgeHabits(habitList); - habitFixtures.createShortHabit(); - habitFixtures.createEmptyHabit(); + fixtures.purgeHabits(habitList); + fixtures.createShortHabit(); + fixtures.createEmptyHabit(); Context targetContext = InstrumentationRegistry.getTargetContext(); baseDir = targetContext.getCacheDir(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java index 4f73471b0..f5e113f9b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java @@ -59,7 +59,7 @@ public class ImportTest extends BaseAndroidTest super.setUp(); DateUtils.setFixedLocalTime(null); - habitFixtures.purgeHabits(habitList); + fixtures.purgeHabits(habitList); context = InstrumentationRegistry.getInstrumentation().getContext(); baseDir = FileUtils.getFilesDir("Backups"); if(baseDir == null) fail("baseDir should not be null"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java deleted file mode 100644 index d64cd3aea..000000000 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/ScoreListTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.unit.models; - -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; - -import org.isoron.uhabits.BaseAndroidTest; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ScoreListTest extends BaseAndroidTest -{ - -} diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java index 51cb53dae..d688a34b2 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java @@ -19,24 +19,21 @@ package org.isoron.uhabits.unit.tasks; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.tasks.ExportCSVTask; -import org.isoron.uhabits.unit.HabitFixtures; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.junit.*; +import org.junit.runner.*; -import java.io.File; -import java.util.List; +import java.io.*; +import java.util.*; -import static junit.framework.Assert.assertTrue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; +import static junit.framework.Assert.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; import static org.hamcrest.core.IsNot.not; @RunWith(AndroidJUnit4.class) @@ -52,7 +49,7 @@ public class ExportCSVTaskTest extends BaseAndroidTest @Test public void testExportCSV() throws Throwable { - habitFixtures.createShortHabit(); + fixtures.createShortHabit(); List habits = habitList.getAll(true); ExportCSVTask task = new ExportCSVTask(habits, null); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java index a5eb79627..f6f283f74 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java @@ -47,7 +47,7 @@ public class CheckmarkWidgetViewTest extends ViewTest super.setUp(); InterfaceUtils.setFixedTheme(R.style.TransparentWidgetTheme); - habit = habitFixtures.createShortHabit(); + habit = fixtures.createShortHabit(); view = new CheckmarkWidgetView(targetContext); view.setHabit(habit); refreshData(view); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java index f43578699..45f6c8b8a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java @@ -39,8 +39,8 @@ public class HabitFrequencyViewTest extends ViewTest { super.setUp(); - habitFixtures.purgeHabits(habitList); - Habit habit = habitFixtures.createLongHabit(); + fixtures.purgeHabits(habitList); + Habit habit = fixtures.createLongHabit(); view = new HabitFrequencyView(targetContext); view.setHabit(habit); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java index f2dcb0721..9a4344775 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java @@ -47,8 +47,8 @@ public class HabitHistoryViewTest extends ViewTest { super.setUp(); - habitFixtures.purgeHabits(habitList); - habit = habitFixtures.createLongHabit(); + fixtures.purgeHabits(habitList); + habit = fixtures.createLongHabit(); view = new HabitHistoryView(targetContext); view.setHabit(habit); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java index 31c0d84a0..0bf209502 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java @@ -42,8 +42,8 @@ public class HabitScoreViewTest extends ViewTest { super.setUp(); - habitFixtures.purgeHabits(habitList); - habit = habitFixtures.createLongHabit(); + fixtures.purgeHabits(habitList); + habit = fixtures.createLongHabit(); view = new HabitScoreView(targetContext); view.setHabit(habit); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java index 9cb86086f..276a798bf 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java @@ -40,8 +40,8 @@ public class HabitStreakViewTest extends ViewTest { super.setUp(); - habitFixtures.purgeHabits(habitList); - Habit habit = habitFixtures.createLongHabit(); + fixtures.purgeHabits(habitList); + Habit habit = fixtures.createLongHabit(); view = new HabitStreakView(targetContext); measureView(dpToPixels(300), dpToPixels(100), view); diff --git a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java index f7c712dfb..29348b0b7 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java +++ b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java @@ -19,7 +19,7 @@ package org.isoron.uhabits.models; -import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.*; /** * A Checkmark represents the completion status of the habit for a given day. @@ -62,6 +62,11 @@ public class Checkmark this.value = value; } + public int compareNewer(Checkmark other) + { + return Long.signum(this.getTimestamp() - other.getTimestamp()); + } + public Habit getHabit() { return habit; 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 054aa35b3..89b05d63a 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -19,16 +19,13 @@ package org.isoron.uhabits.models; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.*; -import java.io.IOException; -import java.io.Writer; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; +import java.io.*; +import java.text.*; +import java.util.*; /** * The collection of {@link Checkmark}s belonging to a habit. @@ -44,20 +41,30 @@ public abstract class CheckmarkList this.habit = habit; } + /** + * Adds all the given checkmarks to the list. + *

+ * This should never be called by the application, since the checkmarks are + * computed automatically from the list of repetitions. + * + * @param checkmarks the checkmarks to be added. + */ + public abstract void add(List checkmarks); + /** * Returns the values for all the checkmarks, since the oldest repetition of - * the habit until today. If there are no repetitions at all, returns an - * empty array. + * the habit until today. *

- * The values are returned in an array containing one integer value for each - * day since the first repetition of the habit until today. The first entry + * If there are no repetitions at all, returns an empty array. The values + * are returned in an array containing one integer value for each day since + * the first repetition of the habit until today. The first entry * corresponds to today, the second entry corresponds to yesterday, and so * on. * * @return values for the checkmarks in the interval */ @NonNull - public int[] getAllValues() + public final int[] getAllValues() { Repetition oldestRep = habit.getRepetitions().getOldest(); if (oldestRep == null) return new int[0]; @@ -68,17 +75,32 @@ public abstract class CheckmarkList return getValues(fromTimestamp, toTimestamp); } + /** + * Returns the list of checkmarks that fall within the given interval. + *

+ * There is exactly one checkmark per day in the interval. The endpoints of + * the interval are included. The list is ordered by timestamp (decreasing). + * That is, the first checkmark corresponds to the newest timestamp, and the + * last checkmark corresponds to the oldest timestamp. + * + * @param fromTimestamp timestamp of the beginning of the interval. + * @param toTimestamp timestamp of the end of the interval. + * @return the list of checkmarks within the interval. + */ + @NonNull + public abstract List getByInterval(long fromTimestamp, + long toTimestamp); + /** * Returns the checkmark for today. * * @return checkmark for today */ @Nullable - public Checkmark getToday() + public final Checkmark getToday() { - long today = DateUtils.getStartOfToday(); - compute(today, today); - return getNewest(); + computeAll(); + return getNewestComputed(); } /** @@ -86,7 +108,7 @@ public abstract class CheckmarkList * * @return value of today's checkmark */ - public int getTodayValue() + public final int getTodayValue() { Checkmark today = getToday(); if (today != null) return today.getValue(); @@ -106,7 +128,17 @@ public abstract class CheckmarkList * @param to timestamp for the newest checkmark * @return values for the checkmarks inside the given interval */ - public abstract int[] getValues(long from, long to); + public final int[] getValues(long from, long to) + { + List checkmarks = getByInterval(from, to); + int values[] = new int[checkmarks.size()]; + + int i = 0; + for (Checkmark c : checkmarks) + values[i++] = c.getValue(); + + return values; + } /** * Marks as invalid every checkmark that has timestamp either equal or newer @@ -119,13 +151,11 @@ public abstract class CheckmarkList /** * Writes the entire list of checkmarks to the given writer, in CSV format. - * There is one line for each checkmark. Each line contains two fields: - * timestamp and value. * * @param out the writer where the CSV will be output * @throws IOException in case write operations fail */ - public void writeCSV(Writer out) throws IOException + public final void writeCSV(Writer out) throws IOException { computeAll(); @@ -149,11 +179,11 @@ public abstract class CheckmarkList * @param from timestamp for the beginning of the interval * @param to timestamp for the end of the interval */ - protected void compute(long from, final long to) + protected final void compute(long from, final long to) { final long day = DateUtils.millisecondsInOneDay; - Checkmark newestCheckmark = getNewest(); + Checkmark newestCheckmark = getNewestComputed(); if (newestCheckmark != null) from = newestCheckmark.getTimestamp() + day; @@ -185,12 +215,16 @@ public abstract class CheckmarkList checks[i] = Checkmark.CHECKED_IMPLICITLY; } + List checkmarks = new LinkedList<>(); - long timestamps[] = new long[nDays]; for (int i = 0; i < nDays; i++) - timestamps[i] = to - i * day; + { + int value = checks[i]; + long timestamp = to - i * day; + checkmarks.add(new Checkmark(habit, timestamp, value)); + } - insert(timestamps, checks); + add(checkmarks); } /** @@ -198,24 +232,21 @@ public abstract class CheckmarkList * repetition until today. Days that already have a corresponding checkmark * are skipped. */ - protected void computeAll() + protected final void computeAll() { Repetition oldest = habit.getRepetitions().getOldest(); if (oldest == null) return; Long today = DateUtils.getStartOfToday(); - compute(oldest.getTimestamp(), today); } /** - * Returns newest checkmark that has already been computed. Ignores any - * checkmark that has timestamp in the future. This does not update the - * cache. + * Returns newest checkmark that has already been computed. + *

+ * Ignores any checkmark that has timestamp in the future. * * @return newest checkmark already computed */ - protected abstract Checkmark getNewest(); - - protected abstract void insert(long timestamps[], int values[]); + protected abstract Checkmark getNewestComputed(); } diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index aeaf152b1..7b0e57ff4 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -19,17 +19,16 @@ package org.isoron.uhabits.models; -import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.net.*; +import android.support.annotation.*; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.utils.DateUtils; +import org.apache.commons.lang3.builder.*; +import org.isoron.uhabits.*; +import org.isoron.uhabits.utils.*; -import java.util.Locale; +import java.util.*; -import javax.inject.Inject; +import javax.inject.*; /** * The thing that the user wants to track. @@ -105,7 +104,7 @@ public class Habit checkmarks = factory.buildCheckmarkList(this); streaks = factory.buildStreakList(this); scores = factory.buildScoreList(this); - repetitions = factory.buidRepetitionList(this); + repetitions = factory.buildRepetitionList(this); } /** @@ -128,7 +127,7 @@ public class Habit checkmarks = factory.buildCheckmarkList(this); streaks = factory.buildStreakList(this); scores = factory.buildScoreList(this); - repetitions = factory.buidRepetitionList(this); + repetitions = factory.buildRepetitionList(this); } /** @@ -235,7 +234,7 @@ public class Habit return freqNum; } - public void setFreqNum(Integer freqNum) + public void setFreqNum(@NonNull Integer freqNum) { this.freqNum = freqNum; } @@ -243,16 +242,18 @@ public class Habit /** * Not currently used. */ + @NonNull public Integer getHighlight() { return highlight; } - public void setHighlight(Integer highlight) + public void setHighlight(@NonNull Integer highlight) { this.highlight = highlight; } + @Nullable public Long getId() { return id; @@ -387,7 +388,7 @@ public class Habit return archived != 0; } - public void setArchived(Integer archived) + public void setArchived(@NonNull Integer archived) { this.archived = archived; } diff --git a/app/src/main/java/org/isoron/uhabits/models/HabitList.java b/app/src/main/java/org/isoron/uhabits/models/HabitList.java index 946aab86b..4b80e7383 100644 --- a/app/src/main/java/org/isoron/uhabits/models/HabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/HabitList.java @@ -19,18 +19,14 @@ package org.isoron.uhabits.models; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import com.opencsv.CSVWriter; +import com.opencsv.*; -import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.*; -import java.io.IOException; -import java.io.Writer; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.io.*; +import java.util.*; /** * An ordered collection of {@link Habit}s. @@ -43,7 +39,8 @@ public abstract class HabitList * Creates a new HabitList. *

* Depending on the implementation, this list can either be empty or be - * populated by some pre-existing habits. + * populated by some pre-existing habits, for example, from a certain + * database. */ public HabitList() { @@ -52,17 +49,24 @@ public abstract class HabitList /** * Inserts a new habit in the list. + *

+ * If the id of the habit is null, the list will assign it a new id, which + * is guaranteed to be unique in the scope of the list. If id is not null, + * the caller should make sure that the list does not already contain + * another habit with same id, otherwise a RuntimeException will be thrown. * * @param habit the habit to be inserted + * @throws IllegalArgumentException if the habit is already on the list. */ - public abstract void add(@NonNull Habit habit); + public abstract void add(@NonNull Habit habit) + throws IllegalArgumentException; /** - * Returns the total number of unarchived habits. + * Returns the total number of active habits. * - * @return number of unarchived habits + * @return number of active habits */ - public abstract int count(); + public abstract int countActive(); /** * Returns the total number of habits, including archived habits. diff --git a/app/src/main/java/org/isoron/uhabits/models/ModelFactory.java b/app/src/main/java/org/isoron/uhabits/models/ModelFactory.java index c1300ac71..6df0002c0 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ModelFactory.java +++ b/app/src/main/java/org/isoron/uhabits/models/ModelFactory.java @@ -20,17 +20,17 @@ package org.isoron.uhabits.models; /** - * Interface implemented by factories that provide concrete implementations - * of the core model classes. + * Interface implemented by factories that provide concrete implementations of + * the core model classes. */ public interface ModelFactory { - RepetitionList buidRepetitionList(Habit habit); - - HabitList buildHabitList(); + RepetitionList buildRepetitionList(Habit habit); CheckmarkList buildCheckmarkList(Habit habit); + HabitList buildHabitList(); + ScoreList buildScoreList(Habit habit); StreakList buildStreakList(Habit habit); diff --git a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java index ffd8a8499..001bf2695 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java +++ b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java @@ -19,8 +19,7 @@ package org.isoron.uhabits.models; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * A ModelObservable allows objects to subscribe themselves to it and receive diff --git a/app/src/main/java/org/isoron/uhabits/models/Repetition.java b/app/src/main/java/org/isoron/uhabits/models/Repetition.java index b3e8b2771..bcfb8bc65 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Repetition.java +++ b/app/src/main/java/org/isoron/uhabits/models/Repetition.java @@ -19,9 +19,9 @@ package org.isoron.uhabits.models; -import android.support.annotation.NonNull; +import android.support.annotation.*; -import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.*; /** * Represents a record that the user has performed a certain habit at a certain diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index aa0511c0a..d4f38e5e3 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -19,15 +19,12 @@ package org.isoron.uhabits.models; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; -import java.util.Arrays; -import java.util.Calendar; -import java.util.HashMap; -import java.util.List; +import java.util.*; /** * The collection of {@link Repetition}s belonging to a habit. @@ -72,15 +69,16 @@ public abstract class RepetitionList /** * Returns the list of repetitions that happened within the given time * interval. - * - * The list is sorted by timestamp in decreasing order. That is, the first - * element corresponds to the most recent timestamp. The endpoints of the - * interval are included. + *

+ * The list is sorted by timestamp in increasing order. That is, the first + * element corresponds to oldest timestamp, while the last element + * corresponds to the newest. The endpoints of the interval are included. * * @param fromTimestamp timestamp of the beginning of the interval * @param toTimestamp timestamp of the end of the interval * @return list of repetitions within given time interval */ + // TODO: Change order timestamp desc public abstract List getByInterval(long fromTimestamp, long toTimestamp); diff --git a/app/src/main/java/org/isoron/uhabits/models/Score.java b/app/src/main/java/org/isoron/uhabits/models/Score.java index 2ad7b5aae..e4f67c34c 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Score.java +++ b/app/src/main/java/org/isoron/uhabits/models/Score.java @@ -19,13 +19,18 @@ package org.isoron.uhabits.models; -import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.*; /** * Represents how strong a habit is at a certain date. */ public class Score { + /** + * Maximum score value attainable by any habit. + */ + public static final int MAX_VALUE = 19259478; + /** * Habit to which this score belongs to. */ @@ -42,11 +47,6 @@ public class Score */ private final Integer value; - /** - * Maximum score value attainable by any habit. - */ - public static final int MAX_VALUE = 19259478; - public Score(Habit habit, Long timestamp, Integer value) { this.habit = habit; diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index ec4ae3d81..983e6c11d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -19,21 +19,15 @@ package org.isoron.uhabits.models; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.*; -import java.io.IOException; -import java.io.Writer; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; +import java.io.*; +import java.text.*; +import java.util.*; -public abstract class ScoreList +public abstract class ScoreList implements Iterable { protected final Habit habit; @@ -53,8 +47,29 @@ public abstract class ScoreList observable = new ModelObservable(); } + /** + * Adds the given scores to the list. + *

+ * This method should not be called by the application, since the scores are + * computed automatically from the list of repetitions. + * + * @param scores the scores to add. + */ + public abstract void add(List scores); + public abstract List getAll(); + /** + * Returns the score that has the given timestamp. + *

+ * If no such score exists, returns null. + * + * @param timestamp the timestamp to find. + * @return the score with given timestamp, or null if none exists. + */ + @Nullable + public abstract Score getByTimestamp(long timestamp); + public ModelObservable getObservable() { return observable; @@ -72,11 +87,20 @@ public abstract class ScoreList /** * Returns the value of the score for a given day. + *

+ * If there is no score at the given timestamp (for example, if the + * timestamp given happens before the first repetition of the habit) then + * returns zero. * * @param timestamp the timestamp of a day - * @return score for that day + * @return score value for that day */ - public abstract int getValue(long timestamp); + public final int getValue(long timestamp) + { + Score s = getByTimestamp(timestamp); + if (s != null) return s.getValue(); + return 0; + } public List groupBy(DateUtils.TruncateField field) { @@ -95,12 +119,18 @@ public abstract class ScoreList */ public abstract void invalidateNewerThan(long timestamp); + @Override + public Iterator iterator() + { + return getAll().iterator(); + } + public void writeCSV(Writer out) throws IOException { computeAll(); SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat(); - for (Score s : getAll()) + for (Score s : this) { String timestamp = dateFormat.format(s.getTimestamp()); String score = @@ -109,8 +139,6 @@ public abstract class ScoreList } } - protected abstract void add(List scores); - /** * Computes and saves the scores that are missing inside a given time * interval. @@ -173,20 +201,21 @@ public abstract class ScoreList } /** - * Returns the score for a certain day. + * Returns the most recent score that has already been computed. + *

+ * If no score has been computed yet, returns null. * - * @param timestamp the timestamp for the day - * @return the score for the day + * @return the newest score computed, or null if none exist */ @Nullable - protected abstract Score get(long timestamp); + protected abstract Score getNewestComputed(); @NonNull private HashMap> getGroupedValues(DateUtils.TruncateField field) { HashMap> groups = new HashMap<>(); - for (Score s : getAll()) + for (Score s : this) { long groupTimestamp = DateUtils.truncate(field, s.getTimestamp()); @@ -199,17 +228,6 @@ public abstract class ScoreList return groups; } - /** - * Returns the most recent score that has already been computed. - *

- * If no score has been computed yet, returns null. - * - * @return the newest score computed, or null if none exist - */ - @Nullable - protected abstract Score getNewestComputed(); - - @NonNull private List groupsToAvgScores(HashMap> groups) { diff --git a/app/src/main/java/org/isoron/uhabits/models/Streak.java b/app/src/main/java/org/isoron/uhabits/models/Streak.java index 53854a798..107e5fe86 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Streak.java +++ b/app/src/main/java/org/isoron/uhabits/models/Streak.java @@ -19,8 +19,8 @@ package org.isoron.uhabits.models; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.isoron.uhabits.utils.DateUtils; +import org.apache.commons.lang3.builder.*; +import org.isoron.uhabits.utils.*; public class Streak { diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java index 870a4f16e..c26b3e284 100644 --- a/app/src/main/java/org/isoron/uhabits/models/StreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java @@ -19,15 +19,11 @@ package org.isoron.uhabits.models; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * The collection of {@link Streak}s that belong to a habit. @@ -80,7 +76,7 @@ public abstract class StreakList List streaks = checkmarksToStreaks(beginning, checks); removeNewestComputed(); - insert(streaks); + add(streaks); } /** @@ -155,7 +151,7 @@ public abstract class StreakList return list; } - protected abstract void insert(@NonNull List streaks); + protected abstract void add(@NonNull List streaks); protected abstract void removeNewestComputed(); } diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryCheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryCheckmarkList.java index f42879deb..d8d45fb2a 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryCheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryCheckmarkList.java @@ -19,13 +19,11 @@ package org.isoron.uhabits.models.memory; -import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.models.CheckmarkList; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.DateUtils; +import android.support.annotation.*; -import java.util.Collections; -import java.util.LinkedList; +import org.isoron.uhabits.models.*; + +import java.util.*; /** * In-memory implementation of {@link CheckmarkList}. @@ -41,20 +39,25 @@ public class MemoryCheckmarkList extends CheckmarkList } @Override - public int[] getValues(long from, long to) + public void add(List checkmarks) + { + list.addAll(checkmarks); + Collections.sort(list, (c1, c2) -> c2.compareNewer(c1)); + } + + @NonNull + @Override + public List getByInterval(long fromTimestamp, long toTimestamp) { - compute(from, to); - if (from > to) return new int[0]; + compute(fromTimestamp, toTimestamp); - int length = (int) ((to - from) / DateUtils.millisecondsInOneDay + 1); - int values[] = new int[length]; + List filtered = new LinkedList<>(); - int k = 0; for (Checkmark c : list) - if(c.getTimestamp() >= from && c.getTimestamp() <= to) - values[k++] = c.getValue(); + if (c.getTimestamp() >= fromTimestamp && + c.getTimestamp() <= toTimestamp) filtered.add(c); - return values; + return filtered; } @Override @@ -69,7 +72,7 @@ public class MemoryCheckmarkList extends CheckmarkList } @Override - protected Checkmark getNewest() + protected Checkmark getNewestComputed() { long newestTimestamp = 0; Checkmark newestCheck = null; @@ -86,17 +89,4 @@ public class MemoryCheckmarkList extends CheckmarkList return newestCheck; } - @Override - protected void insert(long[] timestamps, int[] values) - { - for (int i = 0; i < timestamps.length; i++) - { - long t = timestamps[i]; - int v = values[i]; - list.add(new Checkmark(habit, t, v)); - } - - Collections.sort(list, - (c1, c2) -> (int) (c2.getTimestamp() - c1.getTimestamp())); - } } diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryHabitList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryHabitList.java index deee26b0d..d0f76dbce 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryHabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryHabitList.java @@ -19,14 +19,11 @@ package org.isoron.uhabits.models.memory; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.models.*; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * In-memory implementation of {@link HabitList}. @@ -42,13 +39,21 @@ public class MemoryHabitList extends HabitList } @Override - public void add(Habit habit) + public void add(@NonNull Habit habit) throws IllegalArgumentException { + if (list.contains(habit)) + throw new IllegalArgumentException("habit already added"); + + Long id = habit.getId(); + if (id != null && getById(id) != null) + throw new RuntimeException("duplicate id"); + + if (id == null) habit.setId((long) list.size()); list.addLast(habit); } @Override - public int count() + public int countActive() { int count = 0; for (Habit h : list) if (!h.isArchived()) count++; @@ -61,13 +66,6 @@ public class MemoryHabitList extends HabitList return list.size(); } - @Override - public Habit getById(long id) - { - for (Habit h : list) if (h.getId() == id) return h; - return null; - } - @NonNull @Override public List getAll(boolean includeArchive) @@ -76,6 +74,13 @@ public class MemoryHabitList extends HabitList return getFiltered(habit -> !habit.isArchived()); } + @Override + public Habit getById(long id) + { + for (Habit h : list) if (h.getId() == id) return h; + return null; + } + @Nullable @Override public Habit getByPosition(int position) @@ -84,7 +89,7 @@ public class MemoryHabitList extends HabitList } @Override - public int indexOf(Habit h) + public int indexOf(@NonNull Habit h) { return list.indexOf(h); } diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java index baef4ae40..86e0eb9fe 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryModelFactory.java @@ -19,18 +19,12 @@ package org.isoron.uhabits.models.memory; -import org.isoron.uhabits.models.CheckmarkList; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.models.ModelFactory; -import org.isoron.uhabits.models.RepetitionList; -import org.isoron.uhabits.models.ScoreList; -import org.isoron.uhabits.models.StreakList; +import org.isoron.uhabits.models.*; public class MemoryModelFactory implements ModelFactory { @Override - public RepetitionList buidRepetitionList(Habit habit) + public RepetitionList buildRepetitionList(Habit habit) { return new MemoryRepetitionList(habit); } diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java index 13016e8f2..461de8021 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java @@ -19,16 +19,11 @@ package org.isoron.uhabits.models.memory; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Repetition; -import org.isoron.uhabits.models.RepetitionList; +import org.isoron.uhabits.models.*; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * In-memory implementation of {@link RepetitionList}. @@ -54,6 +49,7 @@ public class MemoryRepetitionList extends RepetitionList public List getByInterval(long fromTimestamp, long toTimestamp) { LinkedList filtered = new LinkedList<>(); + for (Repetition r : list) { long t = r.getTimestamp(); diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java index 4dac68e3a..358415473 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java @@ -19,16 +19,11 @@ package org.isoron.uhabits.models.memory; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.models.ScoreList; +import org.isoron.uhabits.models.*; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; public class MemoryScoreList extends ScoreList { @@ -40,14 +35,6 @@ public class MemoryScoreList extends ScoreList list = new LinkedList<>(); } - @Override - public int getValue(long timestamp) - { - Score s = get(timestamp); - if (s != null) return s.getValue(); - return 0; - } - @Override public void invalidateNewerThan(long timestamp) { @@ -67,17 +54,9 @@ public class MemoryScoreList extends ScoreList return new LinkedList<>(list); } - @Override - protected void add(List scores) - { - list.addAll(scores); - Collections.sort(list, - (s1, s2) -> Long.signum(s2.getTimestamp() - s1.getTimestamp())); - } - - @Override @Nullable - protected Score get(long timestamp) + @Override + public Score getByTimestamp(long timestamp) { computeAll(); for (Score s : list) @@ -86,11 +65,19 @@ public class MemoryScoreList extends ScoreList return null; } + @Override + public void add(List scores) + { + list.addAll(scores); + Collections.sort(list, + (s1, s2) -> Long.signum(s2.getTimestamp() - s1.getTimestamp())); + } + @Nullable @Override protected Score getNewestComputed() { - if(list.isEmpty()) return null; + if (list.isEmpty()) return null; return list.get(0); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryStreakList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryStreakList.java index 4efab4ecf..31fd3de8d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryStreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryStreakList.java @@ -19,14 +19,10 @@ package org.isoron.uhabits.models.memory; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Streak; -import org.isoron.uhabits.models.StreakList; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; public class MemoryStreakList extends StreakList { @@ -43,9 +39,8 @@ public class MemoryStreakList extends StreakList { Streak newest = null; - for(Streak s : list) - if(newest == null || s.getEnd() > newest.getEnd()) - newest = s; + for (Streak s : list) + if (newest == null || s.getEnd() > newest.getEnd()) newest = s; return newest; } @@ -55,8 +50,8 @@ public class MemoryStreakList extends StreakList { LinkedList discard = new LinkedList<>(); - for(Streak s : list) - if(s.getEnd() >= timestamp - DateUtils.millisecondsInOneDay) + for (Streak s : list) + if (s.getEnd() >= timestamp - DateUtils.millisecondsInOneDay) discard.add(s); list.removeAll(discard); @@ -64,7 +59,7 @@ public class MemoryStreakList extends StreakList } @Override - protected void insert(List streaks) + protected void add(List streaks) { list.addAll(streaks); Collections.sort(list, (s1, s2) -> s2.compareNewer(s1)); @@ -74,7 +69,7 @@ public class MemoryStreakList extends StreakList protected void removeNewestComputed() { Streak newest = getNewestComputed(); - if(newest != null) list.remove(newest); + if (newest != null) list.remove(newest); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java index 48db5fe95..904391e36 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java @@ -19,13 +19,7 @@ package org.isoron.uhabits.models.sqlite; -import org.isoron.uhabits.models.CheckmarkList; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.models.ModelFactory; -import org.isoron.uhabits.models.RepetitionList; -import org.isoron.uhabits.models.ScoreList; -import org.isoron.uhabits.models.StreakList; +import org.isoron.uhabits.models.*; /** * Factory that provides models backed by an SQLite database. @@ -33,7 +27,7 @@ import org.isoron.uhabits.models.StreakList; public class SQLModelFactory implements ModelFactory { @Override - public RepetitionList buidRepetitionList(Habit habit) + public RepetitionList buildRepetitionList(Habit habit) { return new SQLiteRepetitionList(habit); } @@ -47,7 +41,7 @@ public class SQLModelFactory implements ModelFactory @Override public HabitList buildHabitList() { - return new SQLiteHabitList(); + return SQLiteHabitList.getInstance(); } @Override 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 ba059e938..d83808c8e 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 @@ -19,20 +19,17 @@ package org.isoron.uhabits.models.sqlite; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.database.sqlite.*; +import android.support.annotation.*; -import com.activeandroid.Cache; -import com.activeandroid.query.Delete; -import com.activeandroid.query.Select; +import com.activeandroid.*; +import com.activeandroid.query.*; -import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.models.CheckmarkList; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; +import org.isoron.uhabits.utils.*; + +import java.util.*; /** * Implementation of a {@link CheckmarkList} that is backed by SQLite. @@ -44,6 +41,47 @@ public class SQLiteCheckmarkList extends CheckmarkList super(habit); } + @Override + public void add(List checkmarks) + { + String query = + "insert into Checkmarks(habit, timestamp, value) values (?,?,?)"; + + SQLiteDatabase db = Cache.openDatabase(); + db.beginTransaction(); + try + { + SQLiteStatement statement = db.compileStatement(query); + + for (Checkmark c : checkmarks) + { + statement.bindLong(1, habit.getId()); + statement.bindLong(2, c.getTimestamp()); + statement.bindLong(3, c.getValue()); + statement.execute(); + } + + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + } + } + + @Override + public List getByInterval(long fromTimestamp, long toTimestamp) + { + computeAll(); + + List records = select() + .and("timestamp >= ?", fromTimestamp) + .and("timestamp <= ?", toTimestamp) + .execute(); + + return toCheckmarks(records); + } + @Override public void invalidateNewerThan(long timestamp) { @@ -57,84 +95,29 @@ public class SQLiteCheckmarkList extends CheckmarkList } @Override - @NonNull - public int[] getValues(long fromTimestamp, long toTimestamp) + @Nullable + protected Checkmark getNewestComputed() { - compute(fromTimestamp, toTimestamp); - - if (fromTimestamp > toTimestamp) return new int[0]; - - String query = "select value, timestamp from Checkmarks where " + - "habit = ? and timestamp >= ? and timestamp <= ?"; - - SQLiteDatabase db = Cache.openDatabase(); - String args[] = { - habit.getId().toString(), - Long.toString(fromTimestamp), - Long.toString(toTimestamp) - }; - Cursor cursor = db.rawQuery(query, args); - - long day = DateUtils.millisecondsInOneDay; - int nDays = (int) ((toTimestamp - fromTimestamp) / day) + 1; - int[] checks = new int[nDays]; - - if (cursor.moveToFirst()) - { - do - { - long timestamp = cursor.getLong(1); - int offset = (int) ((timestamp - fromTimestamp) / day); - checks[nDays - offset - 1] = cursor.getInt(0); - - } while (cursor.moveToNext()); - } - - cursor.close(); - return checks; + CheckmarkRecord record = select().limit(1).executeSingle(); + if (record == null) return null; + return record.toCheckmark(); } - @Override - @Nullable - protected Checkmark getNewest() + @NonNull + private From select() { - CheckmarkRecord record = new Select() + return new Select() .from(CheckmarkRecord.class) .where("habit = ?", habit.getId()) .and("timestamp <= ?", DateUtils.getStartOfToday()) - .orderBy("timestamp desc") - .limit(1) - .executeSingle(); - - if(record == null) return null; - return record.toCheckmark(); + .orderBy("timestamp desc"); } - @Override - protected void insert(long timestamps[], int values[]) + @NonNull + private List toCheckmarks(@NonNull List records) { - String query = - "insert into Checkmarks(habit, timestamp, value) values (?,?,?)"; - - SQLiteDatabase db = Cache.openDatabase(); - db.beginTransaction(); - try - { - SQLiteStatement statement = db.compileStatement(query); - - for (int i = 0; i < timestamps.length; i++) - { - statement.bindLong(1, habit.getId()); - statement.bindLong(2, timestamps[i]); - statement.bindLong(3, values[i]); - statement.execute(); - } - - db.setTransactionSuccessful(); - } - finally - { - db.endTransaction(); - } + List checkmarks = new LinkedList<>(); + for (CheckmarkRecord r : records) checkmarks.add(r.toCheckmark()); + return checkmarks; } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java index 34e97104e..9abd9213b 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java @@ -19,19 +19,14 @@ package org.isoron.uhabits.models.sqlite; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import com.activeandroid.query.From; -import com.activeandroid.query.Select; -import com.activeandroid.query.Update; +import com.activeandroid.query.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * Implementation of a {@link HabitList} that is backed by SQLite. @@ -42,11 +37,19 @@ public class SQLiteHabitList extends HabitList private HashMap cache; - public SQLiteHabitList() + private SQLiteHabitList() { cache = new HashMap<>(); } + /** + * Returns the global list of habits. + *

+ * There is only one list of habit per application, corresponding to the + * habits table of the SQLite database. + * + * @return the global list of habits. + */ public static SQLiteHabitList getInstance() { if (instance == null) instance = new SQLiteHabitList(); @@ -56,15 +59,15 @@ public class SQLiteHabitList extends HabitList @Override public void add(@NonNull Habit habit) { - if(cache.containsValue(habit)) - throw new RuntimeException("habit already in cache"); + if (cache.containsValue(habit)) + throw new IllegalArgumentException("habit already added"); HabitRecord record = new HabitRecord(); record.copyFrom(habit); record.position = countWithArchived(); Long id = habit.getId(); - if(id == null) id = record.save(); + if (id == null) id = record.save(); else record.save(id); habit.setId(id); @@ -72,7 +75,7 @@ public class SQLiteHabitList extends HabitList } @Override - public int count() + public int countActive() { return select().count(); } @@ -128,12 +131,14 @@ public class SQLiteHabitList extends HabitList .where("position = ?", position) .executeSingle(); - return getById(record.getId()); + if(record != null) return getById(record.getId()); + return null; } @Override public int indexOf(@NonNull Habit h) { + if (h.getId() == null) return -1; HabitRecord record = HabitRecord.get(h.getId()); if (record == null) return -1; return record.position; diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java index ea468d32c..620e9816d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java @@ -19,64 +19,66 @@ package org.isoron.uhabits.models.sqlite; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import com.activeandroid.query.Delete; -import com.activeandroid.query.From; -import com.activeandroid.query.Select; +import com.activeandroid.query.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Repetition; -import org.isoron.uhabits.models.RepetitionList; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; +import org.isoron.uhabits.utils.*; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * Implementation of a {@link RepetitionList} that is backed by SQLite. */ public class SQLiteRepetitionList extends RepetitionList { - HashMap cache; - public SQLiteRepetitionList(@NonNull Habit habit) { super(habit); - this.cache = new HashMap<>(); } + /** + * Adds a repetition to the global SQLite database. + *

+ * Given a repetition, this creates and saves the corresponding + * RepetitionRecord to the database. + * + * @param rep the repetition to be added + */ @Override public void add(Repetition rep) { RepetitionRecord record = new RepetitionRecord(); record.copyFrom(rep); - long id = record.save(); - cache.put(id, rep); + record.save(); observable.notifyListeners(); } @Override public List getByInterval(long timeFrom, long timeTo) { - return getFromRecord(selectFromTo(timeFrom, timeTo).execute()); + return toRepetitions(selectFromTo(timeFrom, timeTo).execute()); } @Override + @Nullable public Repetition getByTimestamp(long timestamp) { RepetitionRecord record = select().where("timestamp = ?", timestamp).executeSingle(); - return getFromRecord(record); + + if (record == null) return null; + return record.toRepetition(); } @Override public Repetition getOldest() { RepetitionRecord record = select().limit(1).executeSingle(); - return getFromRecord(record); + if (record == null) return null; + return record.toRepetition(); } @Override @@ -91,38 +93,6 @@ public class SQLiteRepetitionList extends RepetitionList observable.notifyListeners(); } - @NonNull - private List getFromRecord( - @Nullable List records) - { - List reps = new LinkedList<>(); - if (records == null) return reps; - - for (RepetitionRecord record : records) - { - Repetition rep = getFromRecord(record); - reps.add(rep); - } - - return reps; - } - - @Nullable - private Repetition getFromRecord(@Nullable RepetitionRecord record) - { - if (record == null) return null; - - Long id = record.getId(); - - if (!cache.containsKey(id)) - { - Repetition repetition = record.toRepetition(); - cache.put(id, repetition); - } - - return cache.get(id); - } - @NonNull private From select() { @@ -140,4 +110,17 @@ public class SQLiteRepetitionList extends RepetitionList .and("timestamp >= ?", timeFrom) .and("timestamp <= ?", timeTo); } + + @NonNull + private List toRepetitions( + @Nullable List records) + { + List reps = new LinkedList<>(); + if (records == null) return reps; + + for (RepetitionRecord record : records) + reps.add(record.toRepetition()); + + return reps; + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java index 63b68922c..309dd1865 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java @@ -19,23 +19,16 @@ package org.isoron.uhabits.models.sqlite; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.database.sqlite.*; +import android.support.annotation.*; -import com.activeandroid.Cache; -import com.activeandroid.query.Delete; -import com.activeandroid.query.From; -import com.activeandroid.query.Select; -import com.activeandroid.util.SQLiteUtils; +import com.activeandroid.*; +import com.activeandroid.query.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.models.ScoreList; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * Implementation of a ScoreList that is backed by SQLite. @@ -52,62 +45,33 @@ public class SQLiteScoreList extends ScoreList super(habit); } - @Override - public int getValue(long timestamp) - { - computeAll(); - String[] args = {habit.getId().toString(), Long.toString(timestamp)}; - return SQLiteUtils.intQuery( - "select score from Score where habit = ? and timestamp = ?", args); - } - - @Override - public void invalidateNewerThan(long timestamp) - { - new Delete() - .from(ScoreRecord.class) - .where("habit = ?", habit.getId()) - .and("timestamp >= ?", timestamp) - .execute(); - } - @Override @NonNull public List getAll() { + computeAll(); + List records = select().execute(); List scores = new LinkedList<>(); - for(ScoreRecord rec : records) + for (ScoreRecord rec : records) scores.add(rec.toScore()); return scores; } - @Nullable - @Override - protected Score getNewestComputed() - { - ScoreRecord record = select().limit(1).executeSingle(); - if(record == null) return null; - return record.toScore(); - } - @Override - @Nullable - protected Score get(long timestamp) + public void invalidateNewerThan(long timestamp) { - computeAll(); - - ScoreRecord record = - select().where("timestamp = ?", timestamp).executeSingle(); - - if(record == null) return null; - return record.toScore(); + new Delete() + .from(ScoreRecord.class) + .where("habit = ?", habit.getId()) + .and("timestamp >= ?", timestamp) + .execute(); } @Override - protected void add(List scores) + public void add(List scores) { String query = "insert into Score(habit, timestamp, score) values (?,?,?)"; @@ -135,7 +99,29 @@ public class SQLiteScoreList extends ScoreList } } - protected From select() + @Override + @Nullable + public Score getByTimestamp(long timestamp) + { + computeAll(); + + ScoreRecord record = + select().where("timestamp = ?", timestamp).executeSingle(); + + if (record == null) return null; + return record.toScore(); + } + + @Nullable + @Override + protected Score getNewestComputed() + { + ScoreRecord record = select().limit(1).executeSingle(); + if (record == null) return null; + return record.toScore(); + } + + private From select() { return new Select() .from(ScoreRecord.class) diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java index 410a1288e..322fd60e8 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java @@ -19,20 +19,15 @@ package org.isoron.uhabits.models.sqlite; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import com.activeandroid.query.Delete; -import com.activeandroid.query.Select; +import com.activeandroid.query.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Streak; -import org.isoron.uhabits.models.StreakList; -import org.isoron.uhabits.utils.DatabaseUtils; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.records.*; +import org.isoron.uhabits.utils.*; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** * Implementation of a StreakList that is backed by SQLite. @@ -61,7 +56,7 @@ public class SQLiteStreakList extends StreakList public Streak getNewestComputed() { StreakRecord newestRecord = getNewestRecord(); - if(newestRecord == null) return null; + if (newestRecord == null) return null; return newestRecord.toStreak(); } @@ -77,19 +72,8 @@ public class SQLiteStreakList extends StreakList observable.notifyListeners(); } - @Nullable - private StreakRecord getNewestRecord() - { - return new Select() - .from(StreakRecord.class) - .where("habit = ?", habit.getId()) - .orderBy("end desc") - .limit(1) - .executeSingle(); - } - @Override - protected void insert(@NonNull List streaks) + protected void add(@NonNull List streaks) { DatabaseUtils.executeAsTransaction(() -> { for (Streak streak : streaks) @@ -101,6 +85,24 @@ public class SQLiteStreakList extends StreakList }); } + @Override + protected void removeNewestComputed() + { + StreakRecord newestStreak = getNewestRecord(); + if (newestStreak != null) newestStreak.delete(); + } + + @Nullable + private StreakRecord getNewestRecord() + { + return new Select() + .from(StreakRecord.class) + .where("habit = ?", habit.getId()) + .orderBy("end desc") + .limit(1) + .executeSingle(); + } + @NonNull private List recordsToStreaks(List records) { @@ -111,11 +113,4 @@ public class SQLiteStreakList extends StreakList return streaks; } - - @Override - protected void removeNewestComputed() - { - StreakRecord newestStreak = getNewestRecord(); - if (newestStreak != null) newestStreak.delete(); - } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/CheckmarkRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java similarity index 87% rename from app/src/main/java/org/isoron/uhabits/models/sqlite/CheckmarkRecord.java rename to app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java index a35d7a1c9..f3512bd40 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/CheckmarkRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java @@ -17,14 +17,13 @@ * with this program. If not, see . */ -package org.isoron.uhabits.models.sqlite; +package org.isoron.uhabits.models.sqlite.records; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; +import com.activeandroid.*; +import com.activeandroid.annotation.*; -import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.*; /** * The SQLite database record corresponding to a {@link Checkmark}. diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/HabitRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java similarity index 89% rename from app/src/main/java/org/isoron/uhabits/models/sqlite/HabitRecord.java rename to app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java index 052739ef5..e4c2e04d2 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/HabitRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java @@ -17,20 +17,18 @@ * with this program. If not, see . */ -package org.isoron.uhabits.models.sqlite; +package org.isoron.uhabits.models.sqlite.records; -import android.annotation.SuppressLint; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.annotation.*; +import android.support.annotation.*; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; -import com.activeandroid.query.Delete; -import com.activeandroid.util.SQLiteUtils; +import com.activeandroid.*; +import com.activeandroid.annotation.*; +import com.activeandroid.query.*; +import com.activeandroid.util.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; /** * The SQLite database record corresponding to a {@link Habit}. @@ -83,7 +81,7 @@ public class HabitRecord extends Model } @Nullable - public static HabitRecord get(Long id) + public static HabitRecord get(long id) { return HabitRecord.load(HabitRecord.class, id); } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/RepetitionRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java similarity index 86% rename from app/src/main/java/org/isoron/uhabits/models/sqlite/RepetitionRecord.java rename to app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java index e29f792de..ffc524701 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/RepetitionRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java @@ -17,14 +17,13 @@ * with this program. If not, see . */ -package org.isoron.uhabits.models.sqlite; +package org.isoron.uhabits.models.sqlite.records; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; +import com.activeandroid.*; +import com.activeandroid.annotation.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Repetition; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.*; /** * The SQLite database record corresponding to a {@link Repetition}. diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/ScoreRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java similarity index 87% rename from app/src/main/java/org/isoron/uhabits/models/sqlite/ScoreRecord.java rename to app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java index 2efc2e21e..2fd2b91b0 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/ScoreRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java @@ -17,14 +17,13 @@ * with this program. If not, see . */ -package org.isoron.uhabits.models.sqlite; +package org.isoron.uhabits.models.sqlite.records; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; +import com.activeandroid.*; +import com.activeandroid.annotation.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.*; /** * The SQLite database record corresponding to a Score. diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/StreakRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/StreakRecord.java similarity index 87% rename from app/src/main/java/org/isoron/uhabits/models/sqlite/StreakRecord.java rename to app/src/main/java/org/isoron/uhabits/models/sqlite/records/StreakRecord.java index fd4229073..c1b4b0a56 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/StreakRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/StreakRecord.java @@ -17,14 +17,13 @@ * with this program. If not, see . */ -package org.isoron.uhabits.models.sqlite; +package org.isoron.uhabits.models.sqlite.records; -import com.activeandroid.Model; -import com.activeandroid.annotation.Column; -import com.activeandroid.annotation.Table; +import com.activeandroid.*; +import com.activeandroid.annotation.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Streak; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.*; /** * The SQLite database record corresponding to a Streak. diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java index 377edb83a..7215430ee 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java @@ -51,10 +51,10 @@ import java.util.Random; public class HabitScoreView extends ScrollableDataView implements HabitDataView, ModelObservable.Listener { - public static final PorterDuffXfermode XFERMODE_CLEAR = + private static final PorterDuffXfermode XFERMODE_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); - public static final PorterDuffXfermode XFERMODE_SRC = + private static final PorterDuffXfermode XFERMODE_SRC = new PorterDuffXfermode(PorterDuff.Mode.SRC); public static int DEFAULT_BUCKET_SIZES[] = {1, 7, 31, 92, 365}; diff --git a/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java b/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java index 5381590f8..4ef5ae48f 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java @@ -19,25 +19,17 @@ package org.isoron.uhabits.utils; -import android.content.Context; -import android.database.Cursor; -import android.support.annotation.NonNull; - -import com.activeandroid.ActiveAndroid; -import com.activeandroid.Cache; -import com.activeandroid.Configuration; - -import org.isoron.uhabits.BuildConfig; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.models.sqlite.CheckmarkRecord; -import org.isoron.uhabits.models.sqlite.HabitRecord; -import org.isoron.uhabits.models.sqlite.RepetitionRecord; -import org.isoron.uhabits.models.sqlite.ScoreRecord; -import org.isoron.uhabits.models.sqlite.StreakRecord; - -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; +import android.content.*; +import android.database.*; +import android.support.annotation.*; + +import com.activeandroid.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.sqlite.records.*; + +import java.io.*; +import java.text.*; public abstract class DatabaseUtils { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java index 4447ac2c9..e37f59bf1 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java @@ -18,15 +18,14 @@ */ package org.isoron.uhabits.widgets; -import android.app.PendingIntent; -import android.content.Context; -import android.view.View; +import android.app.*; +import android.content.*; +import android.view.*; -import org.isoron.uhabits.HabitBroadcastReceiver; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.widgets.views.CheckmarkWidgetView; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.widgets.views.*; public class CheckmarkWidgetProvider extends BaseWidgetProvider { @@ -39,33 +38,34 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider } @Override - protected void refreshCustomViewData(View view) + protected int getDefaultHeight() { - ((HabitDataView) view).refreshData(); + return 125; } @Override - protected PendingIntent getOnClickPendingIntent(Context context, Habit habit) + protected int getDefaultWidth() { - return HabitBroadcastReceiver.buildCheckIntent(context, habit, null); + return 125; } @Override - protected int getDefaultHeight() + protected int getLayoutId() { - return 125; + return R.layout.widget_wrapper; } @Override - protected int getDefaultWidth() + protected PendingIntent getOnClickPendingIntent(Context context, + Habit habit) { - return 125; + return HabitBroadcastReceiver.buildCheckIntent(context, habit, null); } @Override - protected int getLayoutId() + protected void refreshCustomViewData(View view) { - return R.layout.widget_wrapper; + ((HabitDataView) view).refreshData(); } diff --git a/app/src/test/java/org/isoron/uhabits/models/CheckmarkListTest.java b/app/src/test/java/org/isoron/uhabits/models/CheckmarkListTest.java index 02757d335..502e4fe45 100644 --- a/app/src/test/java/org/isoron/uhabits/models/CheckmarkListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/CheckmarkListTest.java @@ -20,6 +20,7 @@ package org.isoron.uhabits.models; import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.DateUtils; import org.junit.Test; diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java b/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java index a79ad5959..698b67322 100644 --- a/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java @@ -19,20 +19,17 @@ package org.isoron.uhabits.models; -import org.hamcrest.MatcherAssert; -import org.isoron.uhabits.BaseUnitTest; -import org.isoron.uhabits.utils.DateUtils; -import org.junit.Test; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; - -import static junit.framework.Assert.fail; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; +import org.hamcrest.*; +import org.isoron.uhabits.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; + +import java.io.*; +import java.util.*; + +import static junit.framework.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.core.IsEqual.equalTo; public class HabitListTest extends BaseUnitTest @@ -73,7 +70,7 @@ public class HabitListTest extends BaseUnitTest @Test public void test_count() { - assertThat(list.count(), equalTo(6)); + assertThat(list.countActive(), equalTo(6)); } @Test diff --git a/app/src/test/java/org/isoron/uhabits/models/RepetitionListTest.java b/app/src/test/java/org/isoron/uhabits/models/RepetitionListTest.java index 51dae4c14..425a05bca 100644 --- a/app/src/test/java/org/isoron/uhabits/models/RepetitionListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/RepetitionListTest.java @@ -22,6 +22,7 @@ package org.isoron.uhabits.models; import android.support.annotation.NonNull; import org.isoron.uhabits.BaseUnitTest; +import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.DateUtils; import org.junit.After; import org.junit.Before; @@ -33,7 +34,7 @@ import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Random; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; import static org.mockito.Mockito.mock; diff --git a/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java b/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java index 9fdc2d48a..8b1007575 100644 --- a/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java @@ -76,7 +76,7 @@ public class ScoreListTest extends BaseUnitTest int actualValues[] = new int[expectedValues.length]; int i = 0; - for (Score s : habit.getScores().getAll()) + for (Score s : habit.getScores()) actualValues[i++] = s.getValue(); assertThat(actualValues, equalTo(expectedValues)); diff --git a/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java b/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java index 3107b6a1b..a4253d543 100644 --- a/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java @@ -19,16 +19,15 @@ package org.isoron.uhabits.models; -import org.isoron.uhabits.BaseUnitTest; -import org.isoron.uhabits.utils.DateUtils; -import org.junit.Test; +import org.isoron.uhabits.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; -import java.util.List; +import java.util.*; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; public class StreakListTest extends BaseUnitTest { From 9a6dafaa79a0244e2e05b8ef5d4bb3873e090ec3 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 12 Jun 2016 09:23:55 -0400 Subject: [PATCH 019/184] Improve documentation --- .../java/org/isoron/uhabits/models/Habit.java | 5 +- .../models/sqlite/records/package-info.java | 23 +++ .../org/isoron/uhabits/tasks/ProgressBar.java | 10 + ...arWrapper.java => AndroidProgressBar.java} | 21 +- .../org/isoron/uhabits/ui/BaseActivity.java | 28 ++- .../java/org/isoron/uhabits/ui/BaseMenu.java | 68 +++++-- .../org/isoron/uhabits/ui/BaseScreen.java | 97 ++++++--- .../isoron/uhabits/ui/BaseSelectionMenu.java | 64 +++++- .../org/isoron/uhabits/ui/BaseSystem.java | 190 ++++++++++-------- .../habits/list/ListHabitsSelectionMenu.java | 4 +- 10 files changed, 352 insertions(+), 158 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/records/package-info.java rename app/src/main/java/org/isoron/uhabits/ui/{ProgressBarWrapper.java => AndroidProgressBar.java} (83%) diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 7b0e57ff4..00c11d16d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -259,7 +259,7 @@ public class Habit return id; } - public void setId(Long id) + public void setId(@Nullable Long id) { this.id = id; } @@ -267,12 +267,13 @@ public class Habit /** * Name of the habit */ + @NonNull public String getName() { return name; } - public void setName(String name) + public void setName(@NonNull String name) { this.name = name; } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/package-info.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/package-info.java new file mode 100644 index 000000000..379d6a6e0 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 . + */ + +/** + * Provides classes that represent rows in the SQLite database. + */ +package org.isoron.uhabits.models.sqlite.records; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java b/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java index 17c3ebbf1..afbfdac7a 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ProgressBar.java @@ -19,8 +19,18 @@ package org.isoron.uhabits.tasks; +/** + * Simple progress bar, used to indicate the progress of a task. + */ public interface ProgressBar { + /** + * Shows the progress bar. + */ void show(); + + /** + * Hides the progress bar. + */ void hide(); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java b/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java similarity index 83% rename from app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java rename to app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java index 44339ebf6..22604c44c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/ProgressBarWrapper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/AndroidProgressBar.java @@ -19,29 +19,32 @@ package org.isoron.uhabits.ui; -import android.view.View; +import android.view.*; -import org.isoron.uhabits.tasks.ProgressBar; +import org.isoron.uhabits.tasks.*; -public class ProgressBarWrapper implements ProgressBar +/** + * Android implementation of {@link ProgressBar}. + */ +public class AndroidProgressBar implements ProgressBar { private final android.widget.ProgressBar progressBar; - public ProgressBarWrapper(android.widget.ProgressBar progressBar) + public AndroidProgressBar(android.widget.ProgressBar progressBar) { this.progressBar = progressBar; } @Override - public void show() + public void hide() { - progressBar.setIndeterminate(true); - progressBar.setVisibility(View.VISIBLE); + progressBar.setVisibility(View.GONE); } @Override - public void hide() + public void show() { - progressBar.setVisibility(View.GONE); + progressBar.setIndeterminate(true); + progressBar.setVisibility(View.VISIBLE); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java index bdc066fc9..84e66d61e 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java @@ -19,17 +19,25 @@ package org.isoron.uhabits.ui; -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.view.Menu; -import android.view.MenuItem; +import android.content.*; +import android.os.*; +import android.support.annotation.*; +import android.support.v7.app.*; +import android.view.*; -import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.utils.*; /** * Base class for all activities in the application. + *

+ * This class delegates the responsibilities of an Android activity to other + * classes. For example, callbacks related to menus are forwarded to a {@link + * BaseMenu}, while callbacks related to activity results are forwarded to a + * {@link BaseScreen}. + *

+ * A BaseActivity also installs an {@link java.lang.Thread.UncaughtExceptionHandler} + * to the main thread that logs the exception to the disk before the application + * crashes. */ abstract public class BaseActivity extends AppCompatActivity implements Thread.UncaughtExceptionHandler @@ -48,7 +56,8 @@ abstract public class BaseActivity extends AppCompatActivity { if (menu == null) return false; if (baseMenu == null) return false; - return baseMenu.onCreate(getMenuInflater(), menu); + baseMenu.onCreate(getMenuInflater(), menu); + return true; } @Override @@ -79,7 +88,8 @@ abstract public class BaseActivity extends AppCompatActivity { ex.printStackTrace(); new BaseSystem(this).dumpBugReportToFile(); - } catch (Exception e) + } + catch (Exception e) { // ignored } diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java b/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java index ffa30706a..c3f789345 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseMenu.java @@ -19,11 +19,18 @@ package org.isoron.uhabits.ui; -import android.support.annotation.NonNull; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import android.support.annotation.*; +import android.view.*; +import javax.annotation.*; + +/** + * Base class for all the menus in the application. + *

+ * This class receives from BaseActivity all callbacks related to menus, such as + * menu creation and click events. It also handles some implementation details + * of creating menus in Android, such as inflating the resources. + */ public abstract class BaseMenu { private final BaseActivity activity; @@ -33,28 +40,59 @@ public abstract class BaseMenu this.activity = activity; } - public final boolean onCreate(@NonNull MenuInflater inflater, - @NonNull Menu menu) + /** + * Declare that the menu has changed, and should be recreated. + */ + public void invalidate() { - menu.clear(); - inflater.inflate(getMenuResourceId(), menu); - onCreate(menu); - return true; + activity.invalidateOptionsMenu(); } + /** + * Called when the menu is first displayed. + *

+ * The given menu is already inflated and ready to receive items. The + * application should override this method and add items to the menu here. + * + * @param menu the menu that is being created. + */ public void onCreate(@NonNull Menu menu) { } + /** + * Called when the menu is first displayed. + *

+ * This method cannot be overridden. The application should override the + * methods onCreate(Menu) and getMenuResourceId instead. + * + * @param inflater a menu inflater, for creating the menu + * @param menu the menu that is being created. + */ + public final void onCreate(@NonNull MenuInflater inflater, + @NonNull Menu menu) + { + menu.clear(); + inflater.inflate(getMenuResourceId(), menu); + onCreate(menu); + } + + /** + * Called whenever an item on the menu is selected. + * + * @param item the item that was selected. + * @return true if the event was consumed, or false otherwise + */ public boolean onItemSelected(@NonNull MenuItem item) { return true; } + /** + * Returns the id of the resource that should be used to inflate this menu. + * + * @return id of the menu resource. + */ + @Resource protected abstract int getMenuResourceId(); - - public void invalidate() - { - activity.invalidateOptionsMenu(); - } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java index d61ffae53..f6df69818 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java @@ -19,26 +19,30 @@ package org.isoron.uhabits.ui; -import android.content.Intent; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.net.Uri; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatDialogFragment; +import android.content.*; +import android.graphics.*; +import android.graphics.drawable.*; +import android.net.*; +import android.os.*; +import android.support.annotation.*; +import android.support.v7.app.*; import android.support.v7.view.ActionMode; import android.support.v7.widget.Toolbar; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.Toast; +import android.view.*; +import android.widget.*; import org.isoron.uhabits.tasks.ProgressBar; -import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.utils.*; -import java.io.File; +import java.io.*; +/** + * Base class for all screens in the application. + *

+ * Screens are responsible for deciding what root views and what menus should be + * attached to the main window. They are also responsible for showing other + * screens and for receiving their results. + */ public abstract class BaseScreen { protected BaseActivity activity; @@ -56,34 +60,72 @@ public abstract class BaseScreen this.activity = activity; } + /** + * Ends the current selection operation. + */ public void finishSelection() { if (selectionMenu == null) return; selectionMenu.finish(); } + /** + * Returns the progress bar that is currently visible on the screen. + *

+ * If the root view attached to the screen does not provide any progress + * bars, returns null. + * + * @return current progress bar, or null if there are none. + */ @Nullable public ProgressBar getProgressBar() { if (rootView == null) return null; - return new ProgressBarWrapper(rootView.getProgressBar()); + return new AndroidProgressBar(rootView.getProgressBar()); } + /** + * Notifies the screen that its contents should be updated. + */ public void invalidate() { if (rootView == null) return; rootView.invalidate(); } + /** + * Called when another Activity has finished, and has returned some result. + * + * @param requestCode the request code originally supplied to {@link + * android.app.Activity#startActivityForResult(Intent, + * int, Bundle)}. + * @param resultCode the result code sent by the other activity. + * @param data an Intent containing extra data sent by the other + * activity. + * @see {@link android.app.Activity#onActivityResult(int, int, Intent)} + */ public void onResult(int requestCode, int resultCode, Intent data) { } + /** + * Sets the menu to be shown by this screen. + *

+ * This menu will be visible if when there is no active selection operation. + * If the provided menu is null, then no menu will be shown. + * + * @param menu the menu to be shown. + */ public void setMenu(@Nullable BaseMenu menu) { activity.setBaseMenu(menu); } + /** + * Sets the root view for this screen. + * + * @param rootView the root view for this screen. + */ public void setRootView(@Nullable BaseRootView rootView) { this.rootView = rootView; @@ -94,7 +136,7 @@ public abstract class BaseScreen } /** - * Set the menu to be shown when a selection is active on the screen. + * Sets the menu to be shown when a selection is active on the screen. * * @param menu the menu to be shown during a selection */ @@ -103,6 +145,11 @@ public abstract class BaseScreen this.selectionMenu = menu; } + /** + * Shows a message on the screen. + * + * @param stringId the string resource id for this message. + */ public void showMessage(@Nullable Integer stringId) { if (stringId == null) return; @@ -134,14 +181,21 @@ public abstract class BaseScreen } /** - * Instructs the screen to start a selection. If a selection menu was - * provided, this menu will be shown instead of the regular one. + * Instructs the screen to start a selection. + *

+ * If a selection menu was provided, this menu will be shown instead of the + * regular one. */ public void startSelection() { activity.startSupportActionMode(new ActionModeWrapper()); } + protected void showDialog(AppCompatDialogFragment dialog, String tag) + { + dialog.show(activity.getSupportFragmentManager(), tag); + } + private void initToolbar() { if (rootView == null) return; @@ -174,11 +228,6 @@ public abstract class BaseScreen } } - protected void showDialog(AppCompatDialogFragment dialog, String tag) - { - dialog.show(activity.getSupportFragmentManager(), tag); - } - private class ActionModeWrapper implements ActionMode.Callback { @Override @@ -203,7 +252,7 @@ public abstract class BaseScreen public void onDestroyActionMode(@Nullable ActionMode mode) { if (selectionMenu == null) return; - selectionMenu.onDestroy(); + selectionMenu.onFinish(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java index 960c826d2..eb5595afc 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSelectionMenu.java @@ -19,13 +19,22 @@ package org.isoron.uhabits.ui; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; import android.support.v7.view.ActionMode; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; +import android.view.*; +/** + * Base class for all the selection menus in the application. + *

+ * A selection menu is a menu that appears when the screen starts a selection + * operation. It contains actions that modify the selected items, such as delete + * or archive. Since it replaces the toolbar, it also has a title. + *

+ * This class hides many implementation details of creating such menus in + * Android. The interface is supposed to look very similar to {@link BaseMenu}, + * with a few additional methods, such as finishing the selection operation. + * Internally, it uses an {@link ActionMode}. + */ public abstract class BaseSelectionMenu { @Nullable @@ -39,35 +48,69 @@ public abstract class BaseSelectionMenu if (actionMode != null) actionMode.finish(); } + /** + * Declare that the menu has changed, and should be recreated. + */ public void invalidate() { if (actionMode != null) actionMode.invalidate(); } - public final void onCreate(@NonNull MenuInflater menuInflater, + /** + * Called when the menu is first displayed. + *

+ * This method cannot be overridden. The application should override the + * methods onCreate(Menu) and getMenuResourceId instead. + * + * @param inflater a menu inflater, for creating the menu + * @param mode the action mode associated with this menu. + * @param menu the menu that is being created. + */ + public final void onCreate(@NonNull MenuInflater inflater, @NonNull ActionMode mode, @NonNull Menu menu) { this.actionMode = mode; - menuInflater.inflate(getResourceId(), menu); + inflater.inflate(getResourceId(), menu); onCreate(menu); } - public void onDestroy() + /** + * Called when the selection operation is about to finish. + */ + public void onFinish() { } + /** + * Called whenever an item on the menu is selected. + * + * @param item the item that was selected. + * @return true if the event was consumed, or false otherwise + */ public boolean onItemClicked(@NonNull MenuItem item) { return false; } + + /** + * Called whenever the menu is invalidated. + * + * @param menu the menu to be refreshed + * @return true if the menu has changes, false otherwise + */ public boolean onPrepare(@NonNull Menu menu) { return false; } + /** + * Sets the title of the selection menu. + * + * @param title the new title. + */ public void setTitle(String title) { if (actionMode != null) actionMode.setTitle(title); @@ -76,10 +119,9 @@ public abstract class BaseSelectionMenu protected abstract int getResourceId(); /** - * Called when the menu is first created, right after the menu has been - * inflated. + * Called when the menu is first created. * - * @param menu the menu containing the buttons + * @param menu the menu being created */ protected void onCreate(@NonNull Menu menu) { diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java index 8dbb04681..a95f90f01 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java @@ -19,29 +19,31 @@ package org.isoron.uhabits.ui; -import android.content.Context; -import android.os.Environment; -import android.support.annotation.NonNull; -import android.view.WindowManager; - -import org.isoron.uhabits.BuildConfig; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.FileUtils; -import org.isoron.uhabits.utils.ReminderUtils; -import org.isoron.uhabits.widgets.WidgetManager; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.LinkedList; - -import javax.inject.Inject; - +import android.content.*; +import android.os.*; +import android.support.annotation.*; +import android.view.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.utils.*; +import org.isoron.uhabits.widgets.*; + +import java.io.*; +import java.lang.Process; +import java.util.*; + +import javax.inject.*; + +/** + * Base class for all systems class in the application. + *

+ * Classes derived from BaseSystem are responsible for handling events and + * sending requests to the Android operating system. Examples include capturing + * a bug report, obtaining device information, or requesting runtime + * permissions. + */ public class BaseSystem { private Context context; @@ -55,69 +57,16 @@ public class BaseSystem HabitsApplication.getComponent().inject(this); } - public String getLogcat() throws IOException - { - int maxNLines = 250; - StringBuilder builder = new StringBuilder(); - - String[] command = new String[]{"logcat", "-d"}; - Process process = Runtime.getRuntime().exec(command); - - InputStreamReader in = new InputStreamReader(process.getInputStream()); - BufferedReader bufferedReader = new BufferedReader(in); - - LinkedList log = new LinkedList<>(); - - String line; - while ((line = bufferedReader.readLine()) != null) - { - log.addLast(line); - if (log.size() > maxNLines) log.removeFirst(); - } - - for (String l : log) - { - builder.append(l); - builder.append('\n'); - } - - return builder.toString(); - } - - public String getDeviceInfo() - { - if (context == null) return ""; - - StringBuilder b = new StringBuilder(); - WindowManager wm = - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - - b.append( - String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME)); - b.append( - String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE)); - b.append(String.format("OS Version: %s (%s)\n", - java.lang.System.getProperty("os.version"), - android.os.Build.VERSION.INCREMENTAL)); - b.append( - String.format("OS API Level: %s\n", android.os.Build.VERSION.SDK)); - b.append(String.format("Device: %s\n", android.os.Build.DEVICE)); - b.append( - String.format("Model (Product): %s (%s)\n", android.os.Build.MODEL, - android.os.Build.PRODUCT)); - b.append( - String.format("Manufacturer: %s\n", android.os.Build.MANUFACTURER)); - b.append(String.format("Other tags: %s\n", android.os.Build.TAGS)); - b.append(String.format("Screen Width: %s\n", - wm.getDefaultDisplay().getWidth())); - b.append(String.format("Screen Height: %s\n", - wm.getDefaultDisplay().getHeight())); - b.append(String.format("SD Card state: %s\n\n", - Environment.getExternalStorageState())); - - return b.toString(); - } - + /** + * Captures a bug report and saves it to a file in the SD card. + *

+ * The contents of the file are generated by the method {@link + * #getBugReport()}. The file is saved in the apps's external private + * storage. + * + * @return the generated file. + * @throws IOException when I/O errors occur. + */ @NonNull public File dumpBugReportToFile() throws IOException { @@ -138,6 +87,14 @@ public class BaseSystem return logFile; } + /** + * Captures and returns a bug report. + *

+ * The bug report contains some device information and the logcat. + * + * @return a String containing the bug report. + * @throws IOException when any I/O error occur. + */ @NonNull public String getBugReport() throws IOException { @@ -146,6 +103,9 @@ public class BaseSystem return deviceInfo + "\n" + logcat; } + /** + * Recreates all application reminders. + */ public void scheduleReminders() { new BaseTask() @@ -159,6 +119,9 @@ public class BaseSystem }.execute(); } + /** + * Refreshes all application widgets. + */ public void updateWidgets() { new BaseTask() @@ -171,4 +134,59 @@ public class BaseSystem } }.execute(); } + + private String getDeviceInfo() + { + if (context == null) return "null context\n"; + + WindowManager wm = + (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + + return + String.format("App Version Name: %s\n", BuildConfig.VERSION_NAME) + + String.format("App Version Code: %s\n", BuildConfig.VERSION_CODE) + + String.format("OS Version: %s (%s)\n", + System.getProperty("os.version"), Build.VERSION.INCREMENTAL) + + String.format("OS API Level: %s\n", Build.VERSION.SDK) + + String.format("Device: %s\n", Build.DEVICE) + + String.format("Model (Product): %s (%s)\n", Build.MODEL, + Build.PRODUCT) + + String.format("Manufacturer: %s\n", Build.MANUFACTURER) + + String.format("Other tags: %s\n", Build.TAGS) + + String.format("Screen Width: %s\n", + wm.getDefaultDisplay().getWidth()) + + String.format("Screen Height: %s\n", + wm.getDefaultDisplay().getHeight()) + + String.format("External storage state: %s\n\n", + Environment.getExternalStorageState()); + } + + private String getLogcat() throws IOException + { + int maxLineCount = 250; + StringBuilder builder = new StringBuilder(); + + String[] command = new String[]{"logcat", "-d"}; + Process process = Runtime.getRuntime().exec(command); + + InputStreamReader in = new InputStreamReader(process.getInputStream()); + BufferedReader bufferedReader = new BufferedReader(in); + + LinkedList log = new LinkedList<>(); + + String line; + while ((line = bufferedReader.readLine()) != null) + { + log.addLast(line); + if (log.size() > maxLineCount) log.removeFirst(); + } + + for (String l : log) + { + builder.append(l); + builder.append('\n'); + } + + return builder.toString(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java index f38c2d21c..27ae3c4ef 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java @@ -59,10 +59,10 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu } @Override - public void onDestroy() + public void onFinish() { if (adapter != null) adapter.clearSelection(); - super.onDestroy(); + super.onFinish(); } @Override From ca9d56e59ed9644c4297b4688bf4e38998e53436 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 12 Jun 2016 18:22:04 -0400 Subject: [PATCH 020/184] Fix toolbars --- .../org/isoron/uhabits/ui/BaseScreen.java | 58 +++++++++++++++++-- .../uhabits/ui/about/AboutActivity.java | 21 ++++--- .../ui/habits/show/ShowHabitActivity.java | 24 ++++---- .../uhabits/ui/settings/SettingsActivity.java | 11 ++-- .../uhabits/ui/settings/SettingsFragment.java | 16 ++--- 5 files changed, 84 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java index f6df69818..bd803cc4a 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java @@ -31,6 +31,7 @@ import android.support.v7.widget.Toolbar; import android.view.*; import android.widget.*; +import org.isoron.uhabits.*; import org.isoron.uhabits.tasks.ProgressBar; import org.isoron.uhabits.utils.*; @@ -60,6 +61,39 @@ public abstract class BaseScreen this.activity = activity; } + @Deprecated + public static void setupActionBarColor(AppCompatActivity activity, + int color) + { + + Toolbar toolbar = (Toolbar) activity.findViewById(R.id.toolbar); + if (toolbar == null) return; + + activity.setSupportActionBar(toolbar); + + ActionBar actionBar = activity.getSupportActionBar(); + if (actionBar == null) return; + + actionBar.setDisplayHomeAsUpEnabled(true); + + ColorDrawable drawable = new ColorDrawable(color); + actionBar.setBackgroundDrawable(drawable); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + int darkerColor = ColorUtils.mixColors(color, Color.BLACK, 0.75f); + activity.getWindow().setStatusBarColor(darkerColor); + + toolbar.setElevation(InterfaceUtils.dpToPixels(activity, 2)); + + View view = activity.findViewById(R.id.toolbarShadow); + if (view != null) view.setVisibility(View.GONE); + + view = activity.findViewById(R.id.headerShadow); + if (view != null) view.setVisibility(View.GONE); + } + } + /** * Ends the current selection operation. */ @@ -210,6 +244,7 @@ public abstract class BaseScreen int color = rootView.getToolbarColor(); setActionBarColor(actionBar, color); setStatusBarColor(color); + setupToolbarElevation(toolbar); } private void setActionBarColor(@NonNull ActionBar actionBar, int color) @@ -220,12 +255,23 @@ public abstract class BaseScreen private void setStatusBarColor(int baseColor) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - { - int darkerColor = - ColorUtils.mixColors(baseColor, Color.BLACK, 0.75f); - activity.getWindow().setStatusBarColor(darkerColor); - } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; + + int darkerColor = ColorUtils.mixColors(baseColor, Color.BLACK, 0.75f); + activity.getWindow().setStatusBarColor(darkerColor); + } + + private void setupToolbarElevation(Toolbar toolbar) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; + + toolbar.setElevation(InterfaceUtils.dpToPixels(activity, 2)); + + View view = activity.findViewById(R.id.toolbarShadow); + if (view != null) view.setVisibility(View.GONE); + + view = activity.findViewById(R.id.headerShadow); + if (view != null) view.setVisibility(View.GONE); } private class ActionModeWrapper implements ActionMode.Callback diff --git a/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java b/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java index e1c7600a0..8e1b49e14 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/about/AboutActivity.java @@ -19,16 +19,15 @@ package org.isoron.uhabits.ui.about; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.View; -import android.widget.TextView; +import android.content.*; +import android.net.*; +import android.os.*; +import android.view.*; +import android.widget.*; -import org.isoron.uhabits.BuildConfig; -import org.isoron.uhabits.R; -import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.*; +import org.isoron.uhabits.ui.*; +import org.isoron.uhabits.utils.*; /** * Activity that allows the user to see information about the app itself. @@ -76,11 +75,11 @@ public class AboutActivity extends BaseActivity implements View.OnClickListener super.onCreate(savedInstanceState); setContentView(R.layout.about); -// setupSupportActionBar(true); int color = InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); -// setupActionBarColor(color); + + BaseScreen.setupActionBarColor(this, color); TextView tvVersion = (TextView) findViewById(R.id.tvVersion); TextView tvRate = (TextView) findViewById(R.id.tvRate); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java index 365495876..4825f1fa4 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java @@ -19,21 +19,21 @@ package org.isoron.uhabits.ui.habits.show; -import android.content.ContentUris; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.app.ActionBar; +import android.content.*; +import android.net.*; +import android.os.*; +import android.support.v7.app.*; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.ui.BaseActivity; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.*; +import org.isoron.uhabits.utils.*; -import javax.inject.Inject; +import javax.inject.*; /** * Activity that allows the user to see more information about a single habit. + * * Shows all the metadata for the habit, in addition to several charts. */ public class ShowHabitActivity extends BaseActivity @@ -53,7 +53,8 @@ public class ShowHabitActivity extends BaseActivity habit = habitList.getById(ContentUris.parseId(data)); setContentView(R.layout.show_habit_activity); -// setupSupportActionBar(true); + BaseScreen.setupActionBarColor(this, ColorUtils.getColor(this, habit.getColor())); + setupHabitActionBar(); } @@ -65,7 +66,6 @@ public class ShowHabitActivity extends BaseActivity if (actionBar == null) return; actionBar.setTitle(habit.getName()); -// setupActionBarColor(ColorUtils.getColor(this, habit.color)); } public Habit getHabit() diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java index aebebc8aa..c281f23d8 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java @@ -19,11 +19,11 @@ package org.isoron.uhabits.ui.settings; -import android.os.Bundle; +import android.os.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.ui.BaseActivity; -import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.*; +import org.isoron.uhabits.ui.*; +import org.isoron.uhabits.utils.*; /** * Activity that allows the user to view and modify the app settings. @@ -35,10 +35,9 @@ public class SettingsActivity extends BaseActivity { super.onCreate(savedInstanceState); setContentView(R.layout.settings_activity); -// setupSupportActionBar(true); int color = InterfaceUtils.getStyledColor(this, R.attr.aboutScreenColor); -// setupActionBarColor(color); + BaseScreen.setupActionBarColor(this, color); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java index b5f9e0d72..40bcd8c96 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsFragment.java @@ -75,17 +75,11 @@ public class SettingsFragment extends PreferenceFragmentCompat private void setResultOnPreferenceClick(String key, final int result) { Preference pref = findPreference(key); - pref.setOnPreferenceClickListener( - new Preference.OnPreferenceClickListener() - { - @Override - public boolean onPreferenceClick(Preference preference) - { - getActivity().setResult(result); - getActivity().finish(); - return true; - } - }); + pref.setOnPreferenceClickListener(preference -> { + getActivity().setResult(result); + getActivity().finish(); + return true; + }); } @Override From 440706882b2363395af0f0a2aa9634c4141336de Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Tue, 14 Jun 2016 05:30:30 -0400 Subject: [PATCH 021/184] Move command tests to JVM --- app/build.gradle | 1 + .../commands/ArchiveHabitsCommandTest.java | 24 ++++------- .../commands/ChangeHabitColorCommandTest.java | 42 ++++++++----------- .../commands/CreateHabitCommandTest.java | 32 ++++++-------- .../commands/DeleteHabitsCommandTest.java | 17 +++----- .../commands/EditHabitCommandTest.java | 23 ++++------ .../commands/ToggleRepetitionCommandTest.java | 24 ++++------- .../commands/UnarchiveHabitsCommandTest.java | 23 ++++------ 8 files changed, 67 insertions(+), 119 deletions(-) rename app/src/{androidTest/java/org/isoron/uhabits/unit => test/java/org/isoron/uhabits}/commands/ArchiveHabitsCommandTest.java (69%) rename app/src/{androidTest/java/org/isoron/uhabits/unit => test/java/org/isoron/uhabits}/commands/ChangeHabitColorCommandTest.java (70%) rename app/src/{androidTest/java/org/isoron/uhabits/unit => test/java/org/isoron/uhabits}/commands/CreateHabitCommandTest.java (72%) rename app/src/{androidTest/java/org/isoron/uhabits/unit => test/java/org/isoron/uhabits}/commands/DeleteHabitsCommandTest.java (82%) rename app/src/{androidTest/java/org/isoron/uhabits/unit => test/java/org/isoron/uhabits}/commands/EditHabitCommandTest.java (82%) rename app/src/{androidTest/java/org/isoron/uhabits/unit => test/java/org/isoron/uhabits}/commands/ToggleRepetitionCommandTest.java (71%) rename app/src/{androidTest/java/org/isoron/uhabits/unit => test/java/org/isoron/uhabits}/commands/UnarchiveHabitsCommandTest.java (70%) diff --git a/app/build.gradle b/app/build.gradle index b7e988bb1..f224ae953 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -71,6 +71,7 @@ dependencies { compile project(':libs:drag-sort-listview:library') testCompile 'junit:junit:4.12' + testCompile 'org.hamcrest:hamcrest-library:1.3' testCompile 'org.mockito:mockito-core:1.10.19' androidTestCompile 'com.android.support:support-annotations:23.3.0' diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/ArchiveHabitsCommandTest.java similarity index 69% rename from app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java rename to app/src/test/java/org/isoron/uhabits/commands/ArchiveHabitsCommandTest.java index 7c0b492f0..3ef68aaed 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ArchiveHabitsCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/ArchiveHabitsCommandTest.java @@ -17,31 +17,23 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.commands; +package org.isoron.uhabits.commands; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.junit.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.commands.ArchiveHabitsCommand; -import org.isoron.uhabits.models.Habit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import java.util.*; -import java.util.Collections; +import static junit.framework.Assert.*; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ArchiveHabitsCommandTest extends BaseAndroidTest +public class ArchiveHabitsCommandTest extends BaseUnitTest { private ArchiveHabitsCommand command; private Habit habit; + @Override @Before public void setUp() { diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/ChangeHabitColorCommandTest.java similarity index 70% rename from app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java rename to app/src/test/java/org/isoron/uhabits/commands/ChangeHabitColorCommandTest.java index c41656c29..70d60bad5 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ChangeHabitColorCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/ChangeHabitColorCommandTest.java @@ -17,30 +17,24 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.commands; +package org.isoron.uhabits.commands; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.junit.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.commands.ChangeHabitColorCommand; -import org.isoron.uhabits.models.Habit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import java.util.*; -import java.util.LinkedList; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ChangeHabitColorCommandTest extends BaseAndroidTest +public class ChangeHabitColorCommandTest extends BaseUnitTest { private ChangeHabitColorCommand command; + private LinkedList habits; + @Override @Before public void setUp() { @@ -48,7 +42,7 @@ public class ChangeHabitColorCommandTest extends BaseAndroidTest habits = new LinkedList<>(); - for(int i = 0; i < 3; i ++) + for (int i = 0; i < 3; i++) { Habit habit = fixtures.createShortHabit(); habit.setColor(i + 1); @@ -73,16 +67,16 @@ public class ChangeHabitColorCommandTest extends BaseAndroidTest checkNewColors(); } - private void checkOriginalColors() + private void checkNewColors() { - int k = 0; - for(Habit h : habits) - assertThat(h.getColor(), equalTo(++k)); + for (Habit h : habits) + assertThat(h.getColor(), equalTo(0)); } - private void checkNewColors() + private void checkOriginalColors() { - for(Habit h : habits) - assertThat(h.getColor(), equalTo(0)); + int k = 0; + for (Habit h : habits) + assertThat(h.getColor(), equalTo(++k)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/CreateHabitCommandTest.java similarity index 72% rename from app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java rename to app/src/test/java/org/isoron/uhabits/commands/CreateHabitCommandTest.java index 3c3f9ebfb..f5adaf27d 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/CreateHabitCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/CreateHabitCommandTest.java @@ -17,33 +17,26 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.commands; +package org.isoron.uhabits.commands; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.junit.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.commands.CreateHabitCommand; -import org.isoron.uhabits.models.Habit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import java.util.*; -import java.util.List; +import static junit.framework.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.*; -import static junit.framework.Assert.assertTrue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class CreateHabitCommandTest extends BaseAndroidTest +public class CreateHabitCommandTest extends BaseUnitTest { private CreateHabitCommand command; private Habit model; + @Override @Before public void setUp() { @@ -52,8 +45,7 @@ public class CreateHabitCommandTest extends BaseAndroidTest model = new Habit(); model.setName("New habit"); command = new CreateHabitCommand(model); - - fixtures.purgeHabits(habitList); + fixtures.purgeHabits(); } @Test @@ -82,4 +74,6 @@ public class CreateHabitCommandTest extends BaseAndroidTest assertThat(id, equalTo(newId)); assertThat(habit.getName(), equalTo(model.getName())); } + + } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/DeleteHabitsCommandTest.java similarity index 82% rename from app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java rename to app/src/test/java/org/isoron/uhabits/commands/DeleteHabitsCommandTest.java index 565a5fdd9..28055da8b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/DeleteHabitsCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/DeleteHabitsCommandTest.java @@ -17,26 +17,19 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.commands; - -import android.support.test.runner.*; -import android.test.suitebuilder.annotation.*; +package org.isoron.uhabits.commands; import org.isoron.uhabits.*; -import org.isoron.uhabits.commands.*; import org.isoron.uhabits.models.*; import org.junit.*; import org.junit.rules.*; -import org.junit.runner.*; import java.util.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; -@RunWith(AndroidJUnit4.class) -@SmallTest -public class DeleteHabitsCommandTest extends BaseAndroidTest +public class DeleteHabitsCommandTest extends BaseUnitTest { private DeleteHabitsCommand command; @@ -51,7 +44,7 @@ public class DeleteHabitsCommandTest extends BaseAndroidTest { super.setUp(); - fixtures.purgeHabits(habitList); + fixtures.purgeHabits(); habits = new LinkedList<>(); // Habits that should be deleted diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java similarity index 82% rename from app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java rename to app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java index a196c064d..8f7fc1a1c 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/EditHabitCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java @@ -17,25 +17,16 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.commands; +package org.isoron.uhabits.commands; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.junit.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.commands.EditHabitCommand; -import org.isoron.uhabits.models.Habit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class EditHabitCommandTest extends BaseAndroidTest +public class EditHabitCommandTest extends BaseUnitTest { private EditHabitCommand command; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/ToggleRepetitionCommandTest.java similarity index 71% rename from app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java rename to app/src/test/java/org/isoron/uhabits/commands/ToggleRepetitionCommandTest.java index 934d8e654..e9e5fd0a7 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/ToggleRepetitionCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/ToggleRepetitionCommandTest.java @@ -17,31 +17,23 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.commands; +package org.isoron.uhabits.commands; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.DateUtils; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import static junit.framework.Assert.*; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class ToggleRepetitionCommandTest extends BaseAndroidTest +public class ToggleRepetitionCommandTest extends BaseUnitTest { private ToggleRepetitionCommand command; private Habit habit; private long today; + @Override @Before public void setUp() { diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/UnarchiveHabitsCommandTest.java similarity index 70% rename from app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java rename to app/src/test/java/org/isoron/uhabits/commands/UnarchiveHabitsCommandTest.java index d9235599b..1c952e663 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/commands/UnarchiveHabitsCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/UnarchiveHabitsCommandTest.java @@ -17,26 +17,17 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.commands; +package org.isoron.uhabits.commands; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.junit.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.commands.UnarchiveHabitsCommand; -import org.isoron.uhabits.models.Habit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import java.util.*; -import java.util.Collections; +import static junit.framework.Assert.*; -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class UnarchiveHabitsCommandTest extends BaseAndroidTest +public class UnarchiveHabitsCommandTest extends BaseUnitTest { private UnarchiveHabitsCommand command; private Habit habit; From 14364901ff6a1548100a7a237f3485be93b3fe3e Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Tue, 14 Jun 2016 05:38:21 -0400 Subject: [PATCH 022/184] Reorganize instrumented tests --- .../org/isoron/uhabits/BaseAndroidTest.java | 1 - .../views/ViewTest.java => BaseViewTest.java} | 28 +++++------- .../uhabits/{unit => }/HabitFixtures.java | 2 +- .../{unit => }/HabitsApplicationTest.java | 22 +++++----- .../{ui => espresso}/HabitMatchers.java | 2 +- .../{ui => espresso}/HabitViewActions.java | 2 +- .../{ui => espresso}/MainActivityActions.java | 17 ++++--- .../uhabits/{ui => espresso}/MainTest.java | 44 ++++++++++--------- .../ShowHabitActivityActions.java | 2 +- .../{ui => espresso}/SystemHelper.java | 21 ++++++++- .../{unit => }/io/HabitsCSVExporterTest.java | 3 +- .../uhabits/{unit => }/io/ImportTest.java | 3 +- .../{unit => }/tasks/ExportCSVTaskTest.java | 3 +- .../{unit => }/tasks/ExportDBTaskTest.java | 3 +- .../{unit => }/tasks/ImportDataTaskTest.java | 3 +- .../list/views}/CheckmarkButtonViewTest.java | 7 ++- .../list/views}/CheckmarkPanelViewTest.java | 7 ++- .../show}/views/HabitFrequencyViewTest.java | 6 +-- .../show}/views/HabitHistoryViewTest.java | 6 +-- .../show}/views/HabitScoreViewTest.java | 6 +-- .../show}/views/HabitStreakViewTest.java | 6 +-- .../habits/show}/views/RingViewTest.java | 6 +-- .../views/CheckmarkWidgetViewTest.java | 7 ++- 23 files changed, 107 insertions(+), 100 deletions(-) rename app/src/androidTest/java/org/isoron/uhabits/{unit/views/ViewTest.java => BaseViewTest.java} (91%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => }/HabitFixtures.java (98%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => }/HabitsApplicationTest.java (74%) rename app/src/androidTest/java/org/isoron/uhabits/{ui => espresso}/HabitMatchers.java (98%) rename app/src/androidTest/java/org/isoron/uhabits/{ui => espresso}/HabitViewActions.java (99%) rename app/src/androidTest/java/org/isoron/uhabits/{ui => espresso}/MainActivityActions.java (93%) rename app/src/androidTest/java/org/isoron/uhabits/{ui => espresso}/MainTest.java (86%) rename app/src/androidTest/java/org/isoron/uhabits/{ui => espresso}/ShowHabitActivityActions.java (97%) rename app/src/androidTest/java/org/isoron/uhabits/{ui => espresso}/SystemHelper.java (82%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => }/io/HabitsCSVExporterTest.java (97%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => }/io/ImportTest.java (98%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => }/tasks/ExportCSVTaskTest.java (96%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => }/tasks/ExportDBTaskTest.java (95%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => }/tasks/ImportDataTaskTest.java (97%) rename app/src/androidTest/java/org/isoron/uhabits/{unit/ui/habits/list/view => ui/habits/list/views}/CheckmarkButtonViewTest.java (96%) rename app/src/androidTest/java/org/isoron/uhabits/{unit/ui/habits/list/view => ui/habits/list/views}/CheckmarkPanelViewTest.java (92%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => ui/habits/show}/views/HabitFrequencyViewTest.java (93%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => ui/habits/show}/views/HabitHistoryViewTest.java (95%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => ui/habits/show}/views/HabitScoreViewTest.java (94%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => ui/habits/show}/views/HabitStreakViewTest.java (92%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => ui/habits/show}/views/RingViewTest.java (93%) rename app/src/androidTest/java/org/isoron/uhabits/{unit => widgets}/views/CheckmarkWidgetViewTest.java (93%) diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java index 94278eb01..5b94835e2 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseAndroidTest.java @@ -26,7 +26,6 @@ import android.support.test.InstrumentationRegistry; import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.unit.HabitFixtures; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; import org.isoron.uhabits.utils.Preferences; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java similarity index 91% rename from app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java index 1f71cea9a..930028ed6 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java @@ -17,29 +17,21 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.views; +package org.isoron.uhabits; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.SystemClock; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; +import android.graphics.*; +import android.os.*; +import android.view.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.utils.FileUtils; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.utils.*; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; -import static junit.framework.Assert.fail; +import static junit.framework.Assert.*; -public class ViewTest extends BaseAndroidTest +public class BaseViewTest extends BaseAndroidTest { protected static final double DEFAULT_SIMILARITY_CUTOFF = 0.09; public static final int HISTOGRAM_BIN_SIZE = 8; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java b/app/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java similarity index 98% rename from app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java rename to app/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java index 26f792ad5..7547691f3 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit; +package org.isoron.uhabits; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.HabitList; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java b/app/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTest.java similarity index 74% rename from app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java rename to app/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTest.java index 317b8248c..3807cbcd4 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitsApplicationTest.java @@ -17,22 +17,20 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit; +package org.isoron.uhabits; -import android.os.Build; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; +import android.os.*; +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.ui.BaseSystem; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.isoron.uhabits.ui.*; +import org.junit.*; +import org.junit.runner.*; -import java.io.IOException; +import java.io.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; @RunWith(AndroidJUnit4.class) @SmallTest diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/HabitMatchers.java similarity index 98% rename from app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java rename to app/src/androidTest/java/org/isoron/uhabits/espresso/HabitMatchers.java index 3090d3dd0..b3fae7c1b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitMatchers.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/HabitMatchers.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui; +package org.isoron.uhabits.espresso; import android.preference.Preference; import android.view.View; diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/HabitViewActions.java similarity index 99% rename from app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java rename to app/src/androidTest/java/org/isoron/uhabits/espresso/HabitViewActions.java index cf37b0708..156e3bea8 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/HabitViewActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/HabitViewActions.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui; +package org.isoron.uhabits.espresso; import android.support.test.espresso.UiController; import android.support.test.espresso.ViewAction; diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/MainActivityActions.java similarity index 93% rename from app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java rename to app/src/androidTest/java/org/isoron/uhabits/espresso/MainActivityActions.java index c37e50308..bcd31389a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/MainActivityActions.java @@ -17,11 +17,12 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui; +package org.isoron.uhabits.espresso; import android.support.test.espresso.NoMatchingViewException; import android.support.test.espresso.contrib.RecyclerViewActions; +import org.hamcrest.*; import org.isoron.uhabits.R; import org.isoron.uhabits.models.sqlite.records.HabitRecord; @@ -53,8 +54,6 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.startsWith; -import static org.isoron.uhabits.ui.HabitMatchers.containsHabit; -import static org.isoron.uhabits.ui.HabitMatchers.withName; public class MainActivityActions { @@ -93,7 +92,8 @@ public class MainActivityActions onView(withId(R.id.buttonSave)) .perform(click()); - onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) + onData( + Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) .onChildView(withId(R.id.label)); return name; @@ -135,7 +135,8 @@ public class MainActivityActions boolean first = true; for(String name : names) { - onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) + onData( + Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) .onChildView(withId(R.id.label)) .perform(first ? longClick() : click()); @@ -147,7 +148,8 @@ public class MainActivityActions { for(String name : names) onView(withId(R.id.listView)) - .check(matches(not(containsHabit(withName(name))))); + .check(matches(Matchers.not( + HabitMatchers.containsHabit(HabitMatchers.withName(name))))); } public static void assertHabitExists(String name) @@ -160,7 +162,8 @@ public class MainActivityActions public static void assertHabitsExist(List names) { for(String name : names) - onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) + onData( + Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) .check(matches(isDisplayed())); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java similarity index 86% rename from app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java rename to app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java index eba6b19db..db1b1ec10 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui; +package org.isoron.uhabits.espresso; import android.app.Activity; import android.app.Instrumentation; @@ -29,6 +29,7 @@ import android.support.test.espresso.intent.rule.IntentsTestRule; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.LargeTest; +import org.hamcrest.*; import org.isoron.uhabits.R; import org.isoron.uhabits.models.sqlite.records.HabitRecord; import org.isoron.uhabits.utils.DateUtils; @@ -67,21 +68,20 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; -import static org.isoron.uhabits.ui.HabitMatchers.withName; -import static org.isoron.uhabits.ui.HabitViewActions.clickAtRandomLocations; -import static org.isoron.uhabits.ui.HabitViewActions.toggleAllCheckmarks; -import static org.isoron.uhabits.ui.MainActivityActions.addHabit; -import static org.isoron.uhabits.ui.MainActivityActions.assertHabitExists; -import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsDontExist; -import static org.isoron.uhabits.ui.MainActivityActions.assertHabitsExist; -import static org.isoron.uhabits.ui.MainActivityActions.clickMenuItem; -import static org.isoron.uhabits.ui.MainActivityActions.clickSettingsItem; -import static org.isoron.uhabits.ui.MainActivityActions.deleteHabit; -import static org.isoron.uhabits.ui.MainActivityActions.deleteHabits; -import static org.isoron.uhabits.ui.MainActivityActions.selectHabit; -import static org.isoron.uhabits.ui.MainActivityActions.selectHabits; -import static org.isoron.uhabits.ui.MainActivityActions.typeHabitData; -import static org.isoron.uhabits.ui.ShowHabitActivityActions.openHistoryEditor; +import static org.isoron.uhabits.espresso.HabitViewActions.clickAtRandomLocations; +import static org.isoron.uhabits.espresso.HabitViewActions.toggleAllCheckmarks; +import static org.isoron.uhabits.espresso.MainActivityActions.addHabit; +import static org.isoron.uhabits.espresso.MainActivityActions.assertHabitExists; +import static org.isoron.uhabits.espresso.MainActivityActions.assertHabitsDontExist; +import static org.isoron.uhabits.espresso.MainActivityActions.assertHabitsExist; +import static org.isoron.uhabits.espresso.MainActivityActions.clickMenuItem; +import static org.isoron.uhabits.espresso.MainActivityActions.clickSettingsItem; +import static org.isoron.uhabits.espresso.MainActivityActions.deleteHabit; +import static org.isoron.uhabits.espresso.MainActivityActions.deleteHabits; +import static org.isoron.uhabits.espresso.MainActivityActions.selectHabit; +import static org.isoron.uhabits.espresso.MainActivityActions.selectHabits; +import static org.isoron.uhabits.espresso.MainActivityActions.typeHabitData; +import static org.isoron.uhabits.espresso.ShowHabitActivityActions.openHistoryEditor; @RunWith(AndroidJUnit4.class) @LargeTest @@ -190,13 +190,15 @@ public class MainTest { String name = addHabit(true); - onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) + onData( + Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) .onChildView(withId(R.id.checkmarkPanel)) .perform(toggleAllCheckmarks()); Thread.sleep(1200); - onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) + onData( + Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) .onChildView(withId(R.id.label)) .perform(click()); @@ -217,7 +219,8 @@ public class MainTest { String name = addHabit(); - onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) + onData( + Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) .onChildView(withId(R.id.label)) .perform(longClick()); @@ -247,7 +250,8 @@ public class MainTest { String name = addHabit(); - onData(allOf(is(instanceOf(HabitRecord.class)), withName(name))) + onData( + Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) .onChildView(withId(R.id.label)) .perform(click()); diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/ShowHabitActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/ShowHabitActivityActions.java similarity index 97% rename from app/src/androidTest/java/org/isoron/uhabits/ui/ShowHabitActivityActions.java rename to app/src/androidTest/java/org/isoron/uhabits/espresso/ShowHabitActivityActions.java index 31a89c397..5976ec06f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/ShowHabitActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/ShowHabitActivityActions.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui; +package org.isoron.uhabits.espresso; import android.support.test.espresso.matcher.ViewMatchers; diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/SystemHelper.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/SystemHelper.java similarity index 82% rename from app/src/androidTest/java/org/isoron/uhabits/ui/SystemHelper.java rename to app/src/androidTest/java/org/isoron/uhabits/espresso/SystemHelper.java index 807e3b36c..6fb12fede 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/SystemHelper.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/SystemHelper.java @@ -1,4 +1,23 @@ -package org.isoron.uhabits.ui; +/* + * 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.espresso; import android.app.KeyguardManager; import android.content.Context; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java b/app/src/androidTest/java/org/isoron/uhabits/io/HabitsCSVExporterTest.java similarity index 97% rename from app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java rename to app/src/androidTest/java/org/isoron/uhabits/io/HabitsCSVExporterTest.java index d4e35f1e5..5cda1ed99 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/HabitsCSVExporterTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/io/HabitsCSVExporterTest.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.io; +package org.isoron.uhabits.io; import android.content.Context; import android.support.test.InstrumentationRegistry; @@ -25,7 +25,6 @@ import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.FileUtils; import org.junit.Before; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java similarity index 98% rename from app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java rename to app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java index f5e113f9b..431ad7ab0 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.io; +package org.isoron.uhabits.io; import android.content.Context; import android.support.test.InstrumentationRegistry; @@ -28,7 +28,6 @@ import org.isoron.uhabits.BaseAndroidTest; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.FileUtils; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.io.GenericImporter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportCSVTaskTest.java similarity index 96% rename from app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java rename to app/src/androidTest/java/org/isoron/uhabits/tasks/ExportCSVTaskTest.java index d688a34b2..68b1caa46 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportCSVTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportCSVTaskTest.java @@ -17,14 +17,13 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.tasks; +package org.isoron.uhabits.tasks; import android.support.test.runner.*; import android.test.suitebuilder.annotation.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.tasks.*; import org.junit.*; import org.junit.runner.*; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportDBTaskTest.java similarity index 95% rename from app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java rename to app/src/androidTest/java/org/isoron/uhabits/tasks/ExportDBTaskTest.java index e811cabbb..d7b480f4c 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ExportDBTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/tasks/ExportDBTaskTest.java @@ -17,13 +17,12 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.tasks; +package org.isoron.uhabits.tasks; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.tasks.ExportDBTask; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/tasks/ImportDataTaskTest.java similarity index 97% rename from app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java rename to app/src/androidTest/java/org/isoron/uhabits/tasks/ImportDataTaskTest.java index 26c3acd39..094692083 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/tasks/ImportDataTaskTest.java @@ -17,14 +17,13 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.tasks; +package org.isoron.uhabits.tasks; import android.support.annotation.NonNull; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.tasks.ImportDataTask; import org.isoron.uhabits.utils.FileUtils; import org.junit.Before; import org.junit.Test; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonViewTest.java similarity index 96% rename from app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonViewTest.java index 1a99e87df..21f94256f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkButtonViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonViewTest.java @@ -17,14 +17,13 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.ui.habits.list.view; +package org.isoron.uhabits.ui.habits.list.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.ui.habits.list.views.CheckmarkButtonView; -import org.isoron.uhabits.unit.views.ViewTest; +import org.isoron.uhabits.BaseViewTest; import org.isoron.uhabits.utils.ColorUtils; import org.junit.Before; import org.junit.Test; @@ -35,7 +34,7 @@ import java.util.concurrent.CountDownLatch; @RunWith(AndroidJUnit4.class) @SmallTest -public class CheckmarkButtonViewTest extends ViewTest +public class CheckmarkButtonViewTest extends BaseViewTest { public static final String PATH = "ui/habits/list/CheckmarkButtonView/"; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelViewTest.java similarity index 92% rename from app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelViewTest.java index bd57a3818..1a81bfff1 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/ui/habits/list/view/CheckmarkPanelViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkPanelViewTest.java @@ -17,15 +17,14 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.ui.habits.list.view; +package org.isoron.uhabits.ui.habits.list.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.habits.list.views.CheckmarkPanelView; -import org.isoron.uhabits.unit.views.ViewTest; +import org.isoron.uhabits.BaseViewTest; import org.isoron.uhabits.utils.ColorUtils; import org.junit.Before; import org.junit.Test; @@ -35,7 +34,7 @@ import java.util.concurrent.CountDownLatch; @RunWith(AndroidJUnit4.class) @SmallTest -public class CheckmarkPanelViewTest extends ViewTest +public class CheckmarkPanelViewTest extends BaseViewTest { public static final String PATH = "ui/habits/list/CheckmarkPanelView/"; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyViewTest.java similarity index 93% rename from app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyViewTest.java index 45f6c8b8a..a315ee3fb 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyViewTest.java @@ -17,20 +17,20 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.views; +package org.isoron.uhabits.ui.habits.show.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.habits.show.views.HabitFrequencyView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitFrequencyViewTest extends ViewTest +public class HabitFrequencyViewTest extends BaseViewTest { private HabitFrequencyView view; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java similarity index 95% rename from app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java index 9a4344775..5066471e8 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java @@ -17,14 +17,14 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.views; +package org.isoron.uhabits.ui.habits.show.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,7 +36,7 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitHistoryViewTest extends ViewTest +public class HabitHistoryViewTest extends BaseViewTest { private Habit habit; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreViewTest.java similarity index 94% rename from app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreViewTest.java index 0bf209502..114087f28 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreViewTest.java @@ -17,21 +17,21 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.views; +package org.isoron.uhabits.ui.habits.show.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; +import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.habits.show.views.HabitScoreView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitScoreViewTest extends ViewTest +public class HabitScoreViewTest extends BaseViewTest { private Habit habit; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakViewTest.java similarity index 92% rename from app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakViewTest.java index 276a798bf..aad240ac9 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakViewTest.java @@ -17,20 +17,20 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.views; +package org.isoron.uhabits.ui.habits.show.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.habits.show.views.HabitStreakView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitStreakViewTest extends ViewTest +public class HabitStreakViewTest extends BaseViewTest { private HabitStreakView view; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/RingViewTest.java similarity index 93% rename from app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/RingViewTest.java index 0b6ba8c47..90f250b95 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/RingViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/RingViewTest.java @@ -17,14 +17,14 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.views; +package org.isoron.uhabits.ui.habits.show.views; import android.graphics.Color; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import org.isoron.uhabits.*; import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.ui.habits.show.views.RingView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,7 +33,7 @@ import java.io.IOException; @RunWith(AndroidJUnit4.class) @SmallTest -public class RingViewTest extends ViewTest +public class RingViewTest extends BaseViewTest { private RingView view; diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java similarity index 93% rename from app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java index f6f283f74..558c72cb1 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkWidgetViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java @@ -17,16 +17,15 @@ * with this program. If not, see . */ -package org.isoron.uhabits.unit.views; +package org.isoron.uhabits.widgets.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import org.isoron.uhabits.R; +import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.utils.DateUtils; import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.widgets.views.CheckmarkWidgetView; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,7 +34,7 @@ import java.io.IOException; @RunWith(AndroidJUnit4.class) @SmallTest -public class CheckmarkWidgetViewTest extends ViewTest +public class CheckmarkWidgetViewTest extends BaseViewTest { private CheckmarkWidgetView view; From 3d3d5b9b96437733954a13ad7166377b9e966a39 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 15 Jun 2016 05:02:13 -0400 Subject: [PATCH 023/184] Fix top navigation on Settings and About screens --- app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java index 84e66d61e..b8b3eb55c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseActivity.java @@ -54,8 +54,8 @@ abstract public class BaseActivity extends AppCompatActivity @Override public boolean onCreateOptionsMenu(@Nullable Menu menu) { - if (menu == null) return false; - if (baseMenu == null) return false; + if (menu == null) return true; + if (baseMenu == null) return true; baseMenu.onCreate(getMenuInflater(), menu); return true; } From 6484b96e5af2f6636490700b0c984caa60e58705 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 15 Jun 2016 05:04:12 -0400 Subject: [PATCH 024/184] Reformat and reorganize some code --- .../org/isoron/uhabits/ui/BaseSystem.java | 2 +- .../controllers/HabitCardListController.java | 2 +- .../list/views/CheckmarkButtonView.java | 4 +- .../habits/list/views/HabitCardListView.java | 7 - .../ui/habits/show/ShowHabitActivity.java | 31 +- .../ui/habits/show/ShowHabitFragment.java | 15 +- .../ui/habits/show/ShowHabitHelper.java | 128 +++-- .../habits/show/views/HabitFrequencyView.java | 325 +++++------ .../habits/show/views/HabitHistoryView.java | 518 +++++++++--------- .../ui/habits/show/views/HabitScoreView.java | 350 ++++++------ .../ui/habits/show/views/HabitStreakView.java | 185 +++---- .../ui/habits/show/views/RingView.java | 213 +++---- .../habits/show/views/ScrollableDataView.java | 13 +- .../uhabits/ui/settings/SettingsActivity.java | 3 + .../widgets/views/CheckmarkWidgetView.java | 1 - .../widgets/views/GraphWidgetView.java | 1 - .../views/HabitWidgetView.java | 89 +-- 17 files changed, 951 insertions(+), 936 deletions(-) rename app/src/main/java/org/isoron/uhabits/{ui/habits/show => widgets}/views/HabitWidgetView.java (66%) diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java index a95f90f01..e1c7e106c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseSystem.java @@ -161,7 +161,7 @@ public class BaseSystem Environment.getExternalStorageState()); } - private String getLogcat() throws IOException + public String getLogcat() throws IOException { int maxLineCount = 250; StringBuilder builder = new StringBuilder(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java index 2cfb03c98..ebb69d60c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java @@ -306,7 +306,7 @@ public class HabitCardListController implements DragSortListView.DropListener, protected void notifyListener() { - if (habitListener == null) return; + if(selectionListener == null) return; if (activeMode == SELECTION_MODE) selectionListener.onSelectionChange(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java index 2fcd34406..ef6f7f2ca 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/CheckmarkButtonView.java @@ -68,8 +68,8 @@ public class CheckmarkButtonView extends FrameLayout public void toggle() { - value = (value == Checkmark.CHECKED_EXPLICITLY ? Checkmark.UNCHECKED : - Checkmark.CHECKED_EXPLICITLY); +// value = (value == Checkmark.CHECKED_EXPLICITLY ? Checkmark.UNCHECKED : +// Checkmark.CHECKED_EXPLICITLY); performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); postInvalidate(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java index e1d0832a3..52366774e 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardListView.java @@ -111,13 +111,6 @@ public class HabitCardListView extends DragSortListView }); } - public void toggleShowArchived() - { -// showArchived = !showArchived; -// cache.setIncludeArchived(showArchived); -// cache.refreshAllHabits(true); - } - @Override protected void onAttachedToWindow() { diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java index 4825f1fa4..bbd7fa628 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitActivity.java @@ -33,7 +33,7 @@ import javax.inject.*; /** * Activity that allows the user to see more information about a single habit. - * + *

* Shows all the metadata for the habit, in addition to several charts. */ public class ShowHabitActivity extends BaseActivity @@ -43,19 +43,9 @@ public class ShowHabitActivity extends BaseActivity @Inject HabitList habitList; - @Override - protected void onCreate(Bundle savedInstanceState) + public Habit getHabit() { - super.onCreate(savedInstanceState); - HabitsApplication.getComponent().inject(this); - - Uri data = getIntent().getData(); - habit = habitList.getById(ContentUris.parseId(data)); - - setContentView(R.layout.show_habit_activity); - BaseScreen.setupActionBarColor(this, ColorUtils.getColor(this, habit.getColor())); - - setupHabitActionBar(); + return habit; } public void setupHabitActionBar() @@ -68,8 +58,19 @@ public class ShowHabitActivity extends BaseActivity actionBar.setTitle(habit.getName()); } - public Habit getHabit() + @Override + protected void onCreate(Bundle savedInstanceState) { - return habit; + super.onCreate(savedInstanceState); + HabitsApplication.getComponent().inject(this); + + Uri data = getIntent().getData(); + habit = habitList.getById(ContentUris.parseId(data)); + + setContentView(R.layout.show_habit_activity); + BaseScreen.setupActionBarColor(this, + ColorUtils.getColor(this, habit.getColor())); + + setupHabitActionBar(); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index ac2c611fa..56279b767 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -195,16 +195,11 @@ public class ShowHabitFragment extends Fragment public void onModelChange() { refreshData(); - activity.runOnUiThread(new Runnable() - { - @Override - public void run() - { - helper.updateColors(); - helper.updateMainHeader(getView()); - helper.updateCardHeaders(getView()); - if (activity != null) activity.setupHabitActionBar(); - } + activity.runOnUiThread(() -> { + helper.updateColors(); + helper.updateMainHeader(getView()); + helper.updateCardHeaders(getView()); + if (activity != null) activity.setupHabitActionBar(); }); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java index dabc9a3c9..d0a340007 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -19,16 +19,14 @@ package org.isoron.uhabits.ui.habits.show; -import android.content.res.Resources; -import android.view.View; -import android.widget.TextView; +import android.content.res.*; +import android.view.*; +import android.widget.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.ui.habits.show.views.RingView; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.utils.*; public class ShowHabitHelper { @@ -63,44 +61,33 @@ public class ShowHabitHelper resources.getString(R.string.days)); } - void updateScore(View view) + void updateCardHeaders(View view) { - if (fragment.habit == null) return; - if (view == null) return; + updateColor(view, R.id.tvHistory); + updateColor(view, R.id.tvOverview); + updateColor(view, R.id.tvStrength); + updateColor(view, R.id.tvStreaks); + updateColor(view, R.id.tvWeekdayFreq); + updateColor(view, R.id.scoreLabel); + } - float todayPercentage = fragment.todayScore / Score.MAX_VALUE; - float monthDiff = - todayPercentage - (fragment.lastMonthScore / Score.MAX_VALUE); - float yearDiff = - todayPercentage - (fragment.lastYearScore / Score.MAX_VALUE); + void updateColor(View view, int viewId) + { + if (fragment.habit == null || fragment.activity == null) return; - RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); + TextView textView = (TextView) view.findViewById(viewId); int androidColor = - ColorUtils.getColor(fragment.getActivity(), - fragment.habit.getColor()); - scoreRing.setColor(androidColor); - scoreRing.setPercentage(todayPercentage); - - TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel); - TextView monthDiffLabel = - (TextView) view.findViewById(R.id.monthDiffLabel); - TextView yearDiffLabel = - (TextView) view.findViewById(R.id.yearDiffLabel); - - scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); - - String minus = "\u2212"; - monthDiffLabel.setText( - String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), - Math.abs(monthDiff) * 100)); - yearDiffLabel.setText( - String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), - Math.abs(yearDiff) * 100)); + ColorUtils.getColor(fragment.activity, fragment.habit.getColor()); + textView.setTextColor(androidColor); + } - monthDiffLabel.setTextColor( - monthDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); - yearDiffLabel.setTextColor( - yearDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); + void updateColors() + { + fragment.activeColor = ColorUtils.getColor(fragment.getContext(), + fragment.habit.getColor()); + fragment.inactiveColor = + InterfaceUtils.getStyledColor(fragment.getContext(), + R.attr.mediumContrastTextColor); } void updateMainHeader(View view) @@ -129,33 +116,42 @@ public class ShowHabitHelper questionLabel.setVisibility(View.GONE); } - void updateCardHeaders(View view) + void updateScore(View view) { - updateColor(view, R.id.tvHistory); - updateColor(view, R.id.tvOverview); - updateColor(view, R.id.tvStrength); - updateColor(view, R.id.tvStreaks); - updateColor(view, R.id.tvWeekdayFreq); - updateColor(view, R.id.scoreLabel); - } + if (fragment.habit == null) return; + if (view == null) return; - void updateColor(View view, int viewId) - { - if (fragment.habit == null || fragment.activity == null) return; + float todayPercentage = fragment.todayScore / Score.MAX_VALUE; + float monthDiff = + todayPercentage - (fragment.lastMonthScore / Score.MAX_VALUE); + float yearDiff = + todayPercentage - (fragment.lastYearScore / Score.MAX_VALUE); - TextView textView = (TextView) view.findViewById(viewId); - int androidColor = - ColorUtils.getColor(fragment.activity, fragment.habit.getColor()); - textView.setTextColor(androidColor); - } + RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); + int androidColor = ColorUtils.getColor(fragment.getActivity(), + fragment.habit.getColor()); + scoreRing.setColor(androidColor); + scoreRing.setPercentage(todayPercentage); - void updateColors() - { - fragment.activeColor = - ColorUtils.getColor(fragment.getContext(), - fragment.habit.getColor()); - fragment.inactiveColor = - InterfaceUtils.getStyledColor(fragment.getContext(), - R.attr.mediumContrastTextColor); + TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel); + TextView monthDiffLabel = + (TextView) view.findViewById(R.id.monthDiffLabel); + TextView yearDiffLabel = + (TextView) view.findViewById(R.id.yearDiffLabel); + + scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); + + String minus = "\u2212"; + monthDiffLabel.setText( + String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), + Math.abs(monthDiff) * 100)); + yearDiffLabel.setText( + String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), + Math.abs(yearDiff) * 100)); + + monthDiffLabel.setTextColor( + monthDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); + yearDiffLabel.setTextColor( + yearDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java index dd8fbdb22..f24317524 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java @@ -19,48 +19,53 @@ package org.isoron.uhabits.ui.habits.show.views; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.AttributeSet; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.ModelObservable; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Random; - -public class HabitFrequencyView extends ScrollableDataView implements HabitDataView, ModelObservable.Listener +import android.content.*; +import android.graphics.*; +import android.util.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.utils.*; + +import java.text.*; +import java.util.*; + +public class HabitFrequencyView extends ScrollableDataView + implements HabitDataView, ModelObservable.Listener { private Paint pGrid; + private float em; + private Habit habit; + private SimpleDateFormat dfMonth; + private SimpleDateFormat dfYear; private Paint pText, pGraph; + private RectF rect, prevRect; + private int baseSize; + private int paddingTop; private float columnWidth; + private int columnHeight; + private int nColumns; private int textColor; + private int gridColor; + private int[] colors; + private int primaryColor; + private boolean isBackgroundTransparent; private HashMap frequency; @@ -79,40 +84,34 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV init(); } - public void setHabit(Habit habit) + @Override + public void onModelChange() { - this.habit = habit; - createColors(); + refreshData(); } - private void init() + public void refreshData() { - createPaints(); - createColors(); - - dfMonth = DateUtils.getDateFormat("MMM"); - dfYear = DateUtils.getDateFormat("yyyy"); + if (isInEditMode()) generateRandomData(); + else if (habit != null) + { + frequency = habit.getRepetitions().getWeekdayFrequency(); + createColors(); + } - rect = new RectF(); - prevRect = new RectF(); + postInvalidate(); } - private void createColors() + public void setHabit(Habit habit) { - if(habit != null) - { - this.primaryColor = ColorUtils.getColor(getContext(), - habit.getColor()); - } - - textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - gridColor = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor); + this.habit = habit; + createColors(); + } - colors = new int[4]; - colors[0] = gridColor; - colors[3] = primaryColor; - colors[1] = ColorUtils.mixColors(colors[0], colors[3], 0.66f); - colors[2] = ColorUtils.mixColors(colors[0], colors[3], 0.33f); + public void setIsBackgroundTransparent(boolean isBackgroundTransparent) + { + this.isBackgroundTransparent = isBackgroundTransparent; + createColors(); } protected void createPaints() @@ -129,78 +128,27 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(width, height); - } - - @Override - protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) - { - if(height < 9) height = 200; - - baseSize = height / 8; - setScrollerBucketSize(baseSize); - - pText.setTextSize(baseSize * 0.4f); - pGraph.setTextSize(baseSize * 0.4f); - pGraph.setStrokeWidth(baseSize * 0.1f); - pGrid.setStrokeWidth(baseSize * 0.05f); - em = pText.getFontSpacing(); - - columnWidth = baseSize; - columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f); - - columnHeight = 8 * baseSize; - nColumns = (int) (width / columnWidth); - paddingTop = 0; - } - - private float getMaxMonthWidth() - { - float maxMonthWidth = 0; - GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); - - for(int i = 0; i < 12; i++) - { - day.set(Calendar.MONTH, i); - float monthWidth = pText.measureText(dfMonth.format(day.getTime())); - maxMonthWidth = Math.max(maxMonthWidth, monthWidth); - } - - return maxMonthWidth; - } - - public void refreshData() + protected void onAttachedToWindow() { - if(isInEditMode()) generateRandomData(); - else if(habit != null) + super.onAttachedToWindow(); + new BaseTask() { - frequency = habit.getRepetitions().getWeekdayFrequency(); - createColors(); - } - - postInvalidate(); + @Override + protected void doInBackground() + { + refreshData(); + } + }.execute(); + habit.getObservable().addListener(this); + habit.getCheckmarks().observable.addListener(this); } - private void generateRandomData() + @Override + protected void onDetachedFromWindow() { - GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); - date.set(Calendar.DAY_OF_MONTH, 1); - Random rand = new Random(); - frequency.clear(); - - for(int i = 0; i < 40; i++) - { - Integer values[] = new Integer[7]; - for(int j = 0; j < 7; j++) - values[j] = rand.nextInt(5); - - frequency.put(date.getTimeInMillis(), values); - date.add(Calendar.MONTH, -1); - } + habit.getCheckmarks().observable.removeListener(this); + habit.getObservable().removeListener(this); + super.onDetachedFromWindow(); } @Override @@ -223,7 +171,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV currentDate.set(Calendar.DAY_OF_MONTH, 1); currentDate.add(Calendar.MONTH, -nColumns + 2 - getDataOffset()); - for(int i = 0; i < nColumns - 1; i++) + for (int i = 0; i < nColumns - 1; i++) { rect.set(0, 0, columnWidth, columnHeight); rect.offset(i * columnWidth, 0); @@ -233,6 +181,59 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV } } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int width, + int height, + int oldWidth, + int oldHeight) + { + if (height < 9) height = 200; + + baseSize = height / 8; + setScrollerBucketSize(baseSize); + + pText.setTextSize(baseSize * 0.4f); + pGraph.setTextSize(baseSize * 0.4f); + pGraph.setStrokeWidth(baseSize * 0.1f); + pGrid.setStrokeWidth(baseSize * 0.05f); + em = pText.getFontSpacing(); + + columnWidth = baseSize; + columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f); + + columnHeight = 8 * baseSize; + nColumns = (int) (width / columnWidth); + paddingTop = 0; + } + + private void createColors() + { + if (habit != null) + { + this.primaryColor = + ColorUtils.getColor(getContext(), habit.getColor()); + } + + textColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + gridColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + + colors = new int[4]; + colors[0] = gridColor; + colors[3] = primaryColor; + colors[1] = ColorUtils.mixColors(colors[0], colors[3], 0.66f); + colors[2] = ColorUtils.mixColors(colors[0], colors[3], 0.33f); + } + private void drawColumn(Canvas canvas, RectF rect, GregorianCalendar date) { Integer values[] = frequency.get(date.getTimeInMillis()); @@ -246,8 +247,7 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV rect.offset(prevRect.left, prevRect.top + baseSize * j); int i = DateUtils.javaWeekdayToLoopWeekday(localeWeekdayList[j]); - if(values != null) - drawMarker(canvas, rect, values[i]); + if (values != null) drawMarker(canvas, rect, values[i]); rect.offset(0, rowHeight); } @@ -259,19 +259,12 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV { Date time = date.getTime(); - canvas.drawText(dfMonth.format(time), rect.centerX(), rect.centerY() - 0.1f * em, pText); + canvas.drawText(dfMonth.format(time), rect.centerX(), + rect.centerY() - 0.1f * em, pText); - if(date.get(Calendar.MONTH) == 1) - canvas.drawText(dfYear.format(time), rect.centerX(), rect.centerY() + 0.9f * em, pText); - } - - private void drawMarker(Canvas canvas, RectF rect, Integer value) - { - float padding = rect.height() * 0.2f; - float radius = (rect.height() - 2 * padding) / 2.0f / 4.0f * Math.min(value, 4); - - pGraph.setColor(colors[Math.min(3, Math.max(0, value - 1))]); - canvas.drawCircle(rect.centerX(), rect.centerY(), radius, pGraph); + if (date.get(Calendar.MONTH) == 1) + canvas.drawText(dfYear.format(time), rect.centerX(), + rect.centerY() + 0.9f * em, pText); } private void drawGrid(Canvas canvas, RectF rGrid) @@ -283,12 +276,14 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV pText.setColor(textColor); pGrid.setColor(gridColor); - for (String day : DateUtils.getLocaleDayNames(Calendar.SHORT)) { + for (String day : DateUtils.getLocaleDayNames(Calendar.SHORT)) + { canvas.drawText(day, rGrid.right - columnWidth, - rGrid.top + rowHeight / 2 + 0.25f * em, pText); + rGrid.top + rowHeight / 2 + 0.25f * em, pText); pGrid.setStrokeWidth(1f); - canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid); + canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, + pGrid); rGrid.offset(0, rowHeight); } @@ -296,40 +291,58 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid); } - public void setIsBackgroundTransparent(boolean isBackgroundTransparent) + private void drawMarker(Canvas canvas, RectF rect, Integer value) { - this.isBackgroundTransparent = isBackgroundTransparent; - createColors(); - } + float padding = rect.height() * 0.2f; + float radius = + (rect.height() - 2 * padding) / 2.0f / 4.0f * Math.min(value, 4); + pGraph.setColor(colors[Math.min(3, Math.max(0, value - 1))]); + canvas.drawCircle(rect.centerX(), rect.centerY(), radius, pGraph); + } - @Override - protected void onAttachedToWindow() + private void generateRandomData() { - super.onAttachedToWindow(); - new BaseTask() + GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); + date.set(Calendar.DAY_OF_MONTH, 1); + Random rand = new Random(); + frequency.clear(); + + for (int i = 0; i < 40; i++) { - @Override - protected void doInBackground() - { - refreshData(); - } - }.execute(); - habit.getObservable().addListener(this); - habit.getCheckmarks().observable.addListener(this); + Integer values[] = new Integer[7]; + for (int j = 0; j < 7; j++) + values[j] = rand.nextInt(5); + + frequency.put(date.getTimeInMillis(), values); + date.add(Calendar.MONTH, -1); + } } - @Override - protected void onDetachedFromWindow() + private float getMaxMonthWidth() { - habit.getCheckmarks().observable.removeListener(this); - habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); + float maxMonthWidth = 0; + GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); + + for (int i = 0; i < 12; i++) + { + day.set(Calendar.MONTH, i); + float monthWidth = pText.measureText(dfMonth.format(day.getTime())); + maxMonthWidth = Math.max(maxMonthWidth, monthWidth); + } + + return maxMonthWidth; } - @Override - public void onModelChange() + private void init() { - refreshData(); + createPaints(); + createColors(); + + dfMonth = DateUtils.getDateFormat("MMM"); + dfYear = DateUtils.getDateFormat("yyyy"); + + rect = new RectF(); + prevRect = new RectF(); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java index b855b8e79..7f7c9a504 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java @@ -19,61 +19,76 @@ package org.isoron.uhabits.ui.habits.show.views; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.ModelObservable; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.tasks.ToggleRepetitionTask; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.Random; - -public class HabitHistoryView extends ScrollableDataView implements HabitDataView, - ToggleRepetitionTask.Listener, ModelObservable.Listener +import android.content.*; +import android.graphics.*; +import android.graphics.Paint.*; +import android.util.*; +import android.view.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.utils.*; + +import java.text.*; +import java.util.*; + +public class HabitHistoryView extends ScrollableDataView implements + HabitDataView, + ToggleRepetitionTask.Listener, + ModelObservable.Listener { private Habit habit; + private int[] checkmarks; + private Paint pSquareBg, pSquareFg, pTextHeader; + private float squareSpacing; private float squareTextOffset; + private float headerTextOffset; private float columnWidth; + private float columnHeight; + private int nColumns; private SimpleDateFormat dfMonth; + private SimpleDateFormat dfYear; private Calendar baseDate; + private int nDays; - /** 0-based-position of today in the column */ + + /** + * 0-based-position of today in the column + */ private int todayPositionInColumn; + private int colors[]; + private RectF baseLocation; + private int primaryColor; private boolean isBackgroundTransparent; + private int textColor; + private int reverseTextColor; + private boolean isEditable; + private String previousMonth; + + private String previousYear; + + private float headerOverflow = 0; + public HabitHistoryView(Context context) { super(context); @@ -86,37 +101,152 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie init(); } + @Override + public void onLongPress(MotionEvent e) + { + onSingleTapUp(e); + } + + @Override + public void onModelChange() + { + refreshData(); + } + + @Override + public boolean onSingleTapUp(MotionEvent e) + { + if (!isEditable) return false; + + performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + + int pointerId = e.getPointerId(0); + float x = e.getX(pointerId); + float y = e.getY(pointerId); + + final Long timestamp = positionToTimestamp(x, y); + if (timestamp == null) return false; + + ToggleRepetitionTask task = new ToggleRepetitionTask(habit, timestamp); + task.setListener(this); + task.execute(); + + return true; + } + + @Override + public void onToggleRepetitionFinished() + { + new BaseTask() + { + @Override + protected void doInBackground() + { + refreshData(); + } + + @Override + protected void onPostExecute(Void aVoid) + { + invalidate(); + super.onPostExecute(null); + } + }.execute(); + } + + @Override + public void refreshData() + { + if (isInEditMode()) generateRandomData(); + else + { + if (habit == null) return; + checkmarks = habit.getCheckmarks().getAllValues(); + createColors(); + } + + updateDate(); + postInvalidate(); + } + public void setHabit(Habit habit) { this.habit = habit; createColors(); } - private void init() + public void setIsBackgroundTransparent(boolean isBackgroundTransparent) { + this.isBackgroundTransparent = isBackgroundTransparent; createColors(); - createPaints(); + } - isEditable = false; - checkmarks = new int[0]; - primaryColor = ColorUtils.getColor(getContext(), 7); - dfMonth = DateUtils.getDateFormat("MMM"); - dfYear = DateUtils.getDateFormat("yyyy"); + public void setIsEditable(boolean isEditable) + { + this.isEditable = isEditable; + } - baseLocation = new RectF(); + protected void createPaints() + { + pTextHeader = new Paint(); + pTextHeader.setTextAlign(Align.LEFT); + pTextHeader.setAntiAlias(true); + + pSquareBg = new Paint(); + + pSquareFg = new Paint(); + pSquareFg.setAntiAlias(true); + pSquareFg.setTextAlign(Align.CENTER); } - private void updateDate() + @Override + protected void onAttachedToWindow() { - baseDate = DateUtils.getStartOfTodayCalendar(); - baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7); + super.onAttachedToWindow(); + new BaseTask() + { + @Override + protected void doInBackground() + { + refreshData(); + } + }.execute(); + habit.getObservable().addListener(this); + habit.getCheckmarks().observable.addListener(this); + } - nDays = (nColumns - 1) * 7; - int realWeekday = DateUtils.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK); - todayPositionInColumn = (7 + realWeekday - baseDate.getFirstDayOfWeek()) % 7; + @Override + protected void onDetachedFromWindow() + { + habit.getCheckmarks().observable.removeListener(this); + habit.getObservable().removeListener(this); + super.onDetachedFromWindow(); + } - baseDate.add(Calendar.DAY_OF_YEAR, -nDays); - baseDate.add(Calendar.DAY_OF_YEAR, -todayPositionInColumn); + @Override + protected void onDraw(Canvas canvas) + { + super.onDraw(canvas); + + baseLocation.set(0, 0, columnWidth - squareSpacing, + columnWidth - squareSpacing); + baseLocation.offset(getPaddingLeft(), getPaddingTop()); + + headerOverflow = 0; + previousMonth = ""; + previousYear = ""; + pTextHeader.setColor(textColor); + + updateDate(); + GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone(); + + for (int column = 0; column < nColumns - 1; column++) + { + drawColumn(canvas, baseLocation, currentDate, column); + baseLocation.offset(columnWidth, -columnHeight); + } + + drawAxis(canvas, baseLocation); } @Override @@ -128,14 +258,18 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie } @Override - protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) + protected void onSizeChanged(int width, + int height, + int oldWidth, + int oldHeight) { - if(height < 8) height = 200; + if (height < 8) height = 200; float baseSize = height / 8.0f; setScrollerBucketSize((int) baseSize); squareSpacing = InterfaceUtils.dpToPixels(getContext(), 1.0f); - float maxTextSize = getResources().getDimension(R.dimen.regularTextSize); + float maxTextSize = + getResources().getDimension(R.dimen.regularTextSize); float textSize = height * 0.06f; textSize = Math.min(textSize, maxTextSize); @@ -149,35 +283,26 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie columnWidth = baseSize; columnHeight = 8 * baseSize; - nColumns = (int)((width - rightLabelWidth - horizontalPadding) / baseSize) + 1; + nColumns = + (int) ((width - rightLabelWidth - horizontalPadding) / baseSize) + + 1; updateDate(); } - private float getWeekdayLabelWidth() - { - float width = 0; - - for(String w : DateUtils.getLocaleDayNames(Calendar.SHORT)) - width = Math.max(width, pSquareFg.measureText(w)); - - return width; - } - private void createColors() { - if(habit != null) - this.primaryColor = ColorUtils.getColor(getContext(), - habit.getColor()); + if (habit != null) this.primaryColor = + ColorUtils.getColor(getContext(), habit.getColor()); - if(isBackgroundTransparent) + if (isBackgroundTransparent) primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f); int red = Color.red(primaryColor); int green = Color.green(primaryColor); int blue = Color.blue(primaryColor); - if(isBackgroundTransparent) + if (isBackgroundTransparent) { colors = new int[3]; colors[0] = Color.argb(16, 255, 255, 255); @@ -189,100 +314,45 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie else { colors = new int[3]; - colors[0] = InterfaceUtils.getStyledColor(getContext(), R.attr.lowContrastTextColor); + colors[0] = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); colors[1] = Color.argb(127, red, green, blue); colors[2] = primaryColor; - textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); - reverseTextColor = InterfaceUtils.getStyledColor(getContext(), R.attr.highContrastReverseTextColor); + textColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + reverseTextColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.highContrastReverseTextColor); } } - protected void createPaints() - { - pTextHeader = new Paint(); - pTextHeader.setTextAlign(Align.LEFT); - pTextHeader.setAntiAlias(true); - - pSquareBg = new Paint(); - - pSquareFg = new Paint(); - pSquareFg.setAntiAlias(true); - pSquareFg.setTextAlign(Align.CENTER); - } - - public void refreshData() - { - if(isInEditMode()) - generateRandomData(); - else - { - if(habit == null) return; - checkmarks = habit.getCheckmarks().getAllValues(); - createColors(); - } - - updateDate(); - postInvalidate(); - } - - private void generateRandomData() - { - Random random = new Random(); - checkmarks = new int[100]; - - for(int i = 0; i < 100; i++) - if(random.nextFloat() < 0.3) checkmarks[i] = 2; - - for(int i = 0; i < 100 - 7; i++) - { - int count = 0; - for (int j = 0; j < 7; j++) - if(checkmarks[i + j] != 0) - count++; - - if(count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1); - } - } - - private String previousMonth; - private String previousYear; - - @Override - protected void onDraw(Canvas canvas) + private void drawAxis(Canvas canvas, RectF location) { - super.onDraw(canvas); - - baseLocation.set(0, 0, columnWidth - squareSpacing, columnWidth - squareSpacing); - baseLocation.offset(getPaddingLeft(), getPaddingTop()); - - headerOverflow = 0; - previousMonth = ""; - previousYear = ""; - pTextHeader.setColor(textColor); - - updateDate(); - GregorianCalendar currentDate = (GregorianCalendar) baseDate.clone(); + float verticalOffset = pTextHeader.getFontSpacing() * 0.4f; - for (int column = 0; column < nColumns - 1; column++) + for (String day : DateUtils.getLocaleDayNames(Calendar.SHORT)) { - drawColumn(canvas, baseLocation, currentDate, column); - baseLocation.offset(columnWidth, - columnHeight); + location.offset(0, columnWidth); + canvas.drawText(day, location.left + headerTextOffset, + location.centerY() + verticalOffset, pTextHeader); } - - drawAxis(canvas, baseLocation); } - private void drawColumn(Canvas canvas, RectF location, GregorianCalendar date, int column) + private void drawColumn(Canvas canvas, + RectF location, + GregorianCalendar date, + int column) { drawColumnHeader(canvas, location, date); location.offset(0, columnWidth); for (int j = 0; j < 7; j++) { - if (!(column == nColumns - 2 && getDataOffset() == 0 && j > todayPositionInColumn)) + if (!(column == nColumns - 2 && getDataOffset() == 0 && + j > todayPositionInColumn)) { - int checkmarkOffset = getDataOffset() * 7 + nDays - 7 * (column + 1) + - todayPositionInColumn - j; + int checkmarkOffset = + getDataOffset() * 7 + nDays - 7 * (column + 1) + + todayPositionInColumn - j; drawSquare(canvas, location, date, checkmarkOffset); } @@ -291,7 +361,31 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie } } - private void drawSquare(Canvas canvas, RectF location, GregorianCalendar date, + private void drawColumnHeader(Canvas canvas, + RectF location, + GregorianCalendar date) + { + String month = dfMonth.format(date.getTime()); + String year = dfYear.format(date.getTime()); + + String text = null; + if (!month.equals(previousMonth)) text = previousMonth = month; + else if (!year.equals(previousYear)) text = previousYear = year; + + if (text != null) + { + canvas.drawText(text, location.left + headerOverflow, + location.bottom - headerTextOffset, pTextHeader); + headerOverflow += + pTextHeader.measureText(text) + columnWidth * 0.2f; + } + + headerOverflow = Math.max(0, headerOverflow - columnWidth); + } + + private void drawSquare(Canvas canvas, + RectF location, + GregorianCalendar date, int checkmarkOffset) { if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]); @@ -300,74 +394,50 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie pSquareFg.setColor(reverseTextColor); canvas.drawRect(location, pSquareBg); String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH)); - canvas.drawText(text, location.centerX(), location.centerY() + squareTextOffset, pSquareFg); + canvas.drawText(text, location.centerX(), + location.centerY() + squareTextOffset, pSquareFg); } - private void drawAxis(Canvas canvas, RectF location) + private void generateRandomData() { - float verticalOffset = pTextHeader.getFontSpacing() * 0.4f; + Random random = new Random(); + checkmarks = new int[100]; - for (String day : DateUtils.getLocaleDayNames(Calendar.SHORT)) + for (int i = 0; i < 100; i++) + if (random.nextFloat() < 0.3) checkmarks[i] = 2; + + for (int i = 0; i < 100 - 7; i++) { - location.offset(0, columnWidth); - canvas.drawText(day, location.left + headerTextOffset, - location.centerY() + verticalOffset, pTextHeader); + int count = 0; + for (int j = 0; j < 7; j++) + if (checkmarks[i + j] != 0) count++; + + if (count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1); } } - private float headerOverflow = 0; - - private void drawColumnHeader(Canvas canvas, RectF location, GregorianCalendar date) + private float getWeekdayLabelWidth() { - String month = dfMonth.format(date.getTime()); - String year = dfYear.format(date.getTime()); - - String text = null; - if (!month.equals(previousMonth)) - text = previousMonth = month; - else if(!year.equals(previousYear)) - text = previousYear = year; + float width = 0; - if(text != null) - { - canvas.drawText(text, location.left + headerOverflow, location.bottom - headerTextOffset, pTextHeader); - headerOverflow += pTextHeader.measureText(text) + columnWidth * 0.2f; - } + for (String w : DateUtils.getLocaleDayNames(Calendar.SHORT)) + width = Math.max(width, pSquareFg.measureText(w)); - headerOverflow = Math.max(0, headerOverflow - columnWidth); + return width; } - public void setIsBackgroundTransparent(boolean isBackgroundTransparent) + private void init() { - this.isBackgroundTransparent = isBackgroundTransparent; createColors(); - } - - @Override - public void onLongPress(MotionEvent e) - { - onSingleTapUp(e); - } - - @Override - public boolean onSingleTapUp(MotionEvent e) - { - if(!isEditable) return false; - - performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); - - int pointerId = e.getPointerId(0); - float x = e.getX(pointerId); - float y = e.getY(pointerId); - - final Long timestamp = positionToTimestamp(x, y); - if(timestamp == null) return false; + createPaints(); - ToggleRepetitionTask task = new ToggleRepetitionTask(habit, timestamp); - task.setListener(this); - task.execute(); + isEditable = false; + checkmarks = new int[0]; + primaryColor = ColorUtils.getColor(getContext(), 7); + dfMonth = DateUtils.getDateFormat("MMM"); + dfYear = DateUtils.getDateFormat("yyyy"); - return true; + baseLocation = new RectF(); } private Long positionToTimestamp(float x, float y) @@ -375,71 +445,31 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie int col = (int) (x / columnWidth); int row = (int) (y / columnWidth); - if(row == 0) return null; - if(col == nColumns - 1) return null; + if (row == 0) return null; + if (col == nColumns - 1) return null; int offset = col * 7 + (row - 1); Calendar date = (Calendar) baseDate.clone(); date.add(Calendar.DAY_OF_YEAR, offset); - if(DateUtils.getStartOfDay(date.getTimeInMillis()) > DateUtils.getStartOfToday()) - return null; + if (DateUtils.getStartOfDay(date.getTimeInMillis()) > + DateUtils.getStartOfToday()) return null; return date.getTimeInMillis(); } - public void setIsEditable(boolean isEditable) - { - this.isEditable = isEditable; - } - - @Override - public void onToggleRepetitionFinished() - { - new BaseTask() - { - @Override - protected void doInBackground() - { - refreshData(); - } - - @Override - protected void onPostExecute(Void aVoid) - { - invalidate(); - super.onPostExecute(null); - } - }.execute(); - } - - @Override - protected void onAttachedToWindow() + private void updateDate() { - super.onAttachedToWindow(); - new BaseTask() - { - @Override - protected void doInBackground() - { - refreshData(); - } - }.execute(); - habit.getObservable().addListener(this); - habit.getCheckmarks().observable.addListener(this); - } + baseDate = DateUtils.getStartOfTodayCalendar(); + baseDate.add(Calendar.DAY_OF_YEAR, -(getDataOffset() - 1) * 7); - @Override - protected void onDetachedFromWindow() - { - habit.getCheckmarks().observable.removeListener(this); - habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); - } + nDays = (nColumns - 1) * 7; + int realWeekday = + DateUtils.getStartOfTodayCalendar().get(Calendar.DAY_OF_WEEK); + todayPositionInColumn = + (7 + realWeekday - baseDate.getFirstDayOfWeek()) % 7; - @Override - public void onModelChange() - { - refreshData(); + baseDate.add(Calendar.DAY_OF_YEAR, -nDays); + baseDate.add(Calendar.DAY_OF_YEAR, -todayPositionInColumn); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java index 7215430ee..2d125283a 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java @@ -19,34 +19,18 @@ package org.isoron.uhabits.ui.habits.show.views; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.RectF; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.util.Log; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.ModelObservable; -import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; +import android.content.*; +import android.graphics.*; +import android.support.annotation.*; +import android.util.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.utils.*; + +import java.text.*; +import java.util.*; public class HabitScoreView extends ScrollableDataView implements HabitDataView, ModelObservable.Listener @@ -138,10 +122,8 @@ public class HabitScoreView extends ScrollableDataView else { if (habit == null) return; - if (bucketSize == 1) - scores = habit.getScores().getAll(); - else - scores = habit.getScores().groupBy(getTruncateField()); + if (bucketSize == 1) scores = habit.getScores().getAll(); + else scores = habit.getScores().groupBy(getTruncateField()); createColors(); } @@ -168,19 +150,6 @@ public class HabitScoreView extends ScrollableDataView requestLayout(); } - private void createColors() - { - if (habit != null) this.primaryColor = - ColorUtils.getColor(getContext(), habit.getColor()); - - textColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.mediumContrastTextColor); - gridColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.lowContrastTextColor); - backgroundColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.cardBackgroundColor); - } - protected void createPaints() { pText = new Paint(); @@ -194,6 +163,158 @@ public class HabitScoreView extends ScrollableDataView pGrid.setAntiAlias(true); } + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + new BaseTask() + { + @Override + protected void doInBackground() + { + refreshData(); + } + }.execute(); + habit.getObservable().addListener(this); + habit.getScores().getObservable().addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + habit.getScores().getObservable().removeListener(this); + habit.getObservable().removeListener(this); + super.onDetachedFromWindow(); + } + + @Override + protected void onDraw(Canvas canvas) + { + super.onDraw(canvas); + Canvas activeCanvas; + + if (isTransparencyEnabled) + { + if (drawingCache == null) initCache(getWidth(), getHeight()); + + activeCanvas = cacheCanvas; + drawingCache.eraseColor(Color.TRANSPARENT); + } + else + { + activeCanvas = canvas; + } + + if (habit == null || scores == null) return; + + rect.set(0, 0, nColumns * columnWidth, columnHeight); + rect.offset(0, paddingTop); + + drawGrid(activeCanvas, rect); + + pText.setColor(textColor); + pGraph.setColor(primaryColor); + prevRect.setEmpty(); + + previousMonthText = ""; + previousYearText = ""; + skipYear = 0; + + long currentDate = DateUtils.getStartOfToday(); + + for (int k = 0; k < nColumns + getDataOffset() - 1; k++) + currentDate -= bucketSize * DateUtils.millisecondsInOneDay; + + for (int k = 0; k < nColumns; k++) + { + int score = 0; + int offset = nColumns - k - 1 + getDataOffset(); + if (offset < scores.size()) score = scores.get(offset).getValue(); + + double relativeScore = ((double) score) / Score.MAX_VALUE; + int height = (int) (columnHeight * relativeScore); + + rect.set(0, 0, baseSize, baseSize); + rect.offset(k * columnWidth + (columnWidth - baseSize) / 2, + paddingTop + columnHeight - height - baseSize / 2); + + if (!prevRect.isEmpty()) + { + drawLine(activeCanvas, prevRect, rect); + drawMarker(activeCanvas, prevRect); + } + + if (k == nColumns - 1) drawMarker(activeCanvas, rect); + + prevRect.set(rect); + rect.set(0, 0, columnWidth, columnHeight); + rect.offset(k * columnWidth, paddingTop); + + drawFooter(activeCanvas, rect, currentDate); + + currentDate += bucketSize * DateUtils.millisecondsInOneDay; + } + + if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int width, + int height, + int oldWidth, + int oldHeight) + { + if (height < 9) height = 200; + + float maxTextSize = getResources().getDimension(R.dimen.tinyTextSize); + float textSize = height * 0.06f; + pText.setTextSize(Math.min(textSize, maxTextSize)); + em = pText.getFontSpacing(); + + footerHeight = (int) (3 * em); + paddingTop = (int) (em); + + baseSize = (height - footerHeight - paddingTop) / 8; + setScrollerBucketSize(baseSize); + + columnWidth = baseSize; + columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f); + columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f); + + nColumns = (int) (width / columnWidth); + columnWidth = (float) width / nColumns; + + columnHeight = 8 * baseSize; + + float minStrokeWidth = InterfaceUtils.dpToPixels(getContext(), 1); + pGraph.setTextSize(baseSize * 0.5f); + pGraph.setStrokeWidth(baseSize * 0.1f); + pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f)); + + if (isTransparencyEnabled) initCache(width, height); + } + + private void createColors() + { + if (habit != null) this.primaryColor = + ColorUtils.getColor(getContext(), habit.getColor()); + + textColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + gridColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + backgroundColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.cardBackgroundColor); + } + private void drawFooter(Canvas canvas, RectF rect, long currentDate) { String yearText = dfYear.format(currentDate); @@ -393,145 +514,6 @@ public class HabitScoreView extends ScrollableDataView cacheCanvas = new Canvas(drawingCache); } - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - new BaseTask() - { - @Override - protected void doInBackground() - { - refreshData(); - } - }.execute(); - habit.getObservable().addListener(this); - habit.getScores().getObservable().addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - habit.getScores().getObservable().removeListener(this); - habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); - } - - @Override - protected void onDraw(Canvas canvas) - { - super.onDraw(canvas); - Canvas activeCanvas; - - if (isTransparencyEnabled) - { - if (drawingCache == null) initCache(getWidth(), getHeight()); - - activeCanvas = cacheCanvas; - drawingCache.eraseColor(Color.TRANSPARENT); - } - else - { - activeCanvas = canvas; - } - - if (habit == null || scores == null) return; - - rect.set(0, 0, nColumns * columnWidth, columnHeight); - rect.offset(0, paddingTop); - - drawGrid(activeCanvas, rect); - - pText.setColor(textColor); - pGraph.setColor(primaryColor); - prevRect.setEmpty(); - - previousMonthText = ""; - previousYearText = ""; - skipYear = 0; - - long currentDate = DateUtils.getStartOfToday(); - - for (int k = 0; k < nColumns + getDataOffset() - 1; k++) - currentDate -= bucketSize * DateUtils.millisecondsInOneDay; - - for (int k = 0; k < nColumns; k++) - { - int score = 0; - int offset = nColumns - k - 1 + getDataOffset(); - if (offset < scores.size()) score = scores.get(offset).getValue(); - - double relativeScore = ((double) score) / Score.MAX_VALUE; - int height = (int) (columnHeight * relativeScore); - - rect.set(0, 0, baseSize, baseSize); - rect.offset(k * columnWidth + (columnWidth - baseSize) / 2, - paddingTop + columnHeight - height - baseSize / 2); - - if (!prevRect.isEmpty()) - { - drawLine(activeCanvas, prevRect, rect); - drawMarker(activeCanvas, prevRect); - } - - if (k == nColumns - 1) drawMarker(activeCanvas, rect); - - prevRect.set(rect); - rect.set(0, 0, columnWidth, columnHeight); - rect.offset(k * columnWidth, paddingTop); - - drawFooter(activeCanvas, rect, currentDate); - - currentDate += bucketSize * DateUtils.millisecondsInOneDay; - } - - if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(width, height); - } - - @Override - protected void onSizeChanged(int width, - int height, - int oldWidth, - int oldHeight) - { - if (height < 9) height = 200; - - float maxTextSize = getResources().getDimension(R.dimen.tinyTextSize); - float textSize = height * 0.06f; - pText.setTextSize(Math.min(textSize, maxTextSize)); - em = pText.getFontSpacing(); - - footerHeight = (int) (3 * em); - paddingTop = (int) (em); - - baseSize = (height - footerHeight - paddingTop) / 8; - setScrollerBucketSize(baseSize); - - columnWidth = baseSize; - columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f); - columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f); - - nColumns = (int) (width / columnWidth); - columnWidth = (float) width / nColumns; - - columnHeight = 8 * baseSize; - - float minStrokeWidth = InterfaceUtils.dpToPixels(getContext(), 1); - pGraph.setTextSize(baseSize * 0.5f); - pGraph.setStrokeWidth(baseSize * 0.1f); - pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f)); - - if (isTransparencyEnabled) initCache(width, height); - } - private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color) { if (isTransparencyEnabled) p.setXfermode(mode); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java index 59d2bc78a..68ebdf934 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java @@ -19,27 +19,18 @@ package org.isoron.uhabits.ui.habits.show.views; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.View; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.ModelObservable; -import org.isoron.uhabits.models.Streak; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.InterfaceUtils; - -import java.text.DateFormat; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.TimeZone; +import android.content.*; +import android.graphics.*; +import android.util.*; +import android.view.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.utils.*; + +import java.text.*; +import java.util.*; public class HabitStreakView extends View implements HabitDataView, ModelObservable.Listener @@ -124,6 +115,82 @@ public class HabitStreakView extends View createColors(); } + protected void createPaints() + { + paint = new Paint(); + paint.setTextAlign(Paint.Align.CENTER); + paint.setAntiAlias(true); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + new BaseTask() + { + @Override + protected void doInBackground() + { + refreshData(); + } + }.execute(); + habit.getObservable().addListener(this); + habit.getStreaks().getObservable().addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + habit.getStreaks().getObservable().removeListener(this); + habit.getObservable().removeListener(this); + super.onDetachedFromWindow(); + } + + @Override + protected void onDraw(Canvas canvas) + { + super.onDraw(canvas); + if (streaks.size() == 0) return; + + rect.set(0, 0, width, baseSize); + + for (Streak s : streaks) + { + drawRow(canvas, s, rect); + rect.offset(0, baseSize); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int width, + int height, + int oldWidth, + int oldHeight) + { + maxStreakCount = height / baseSize; + this.width = width; + + float minTextSize = getResources().getDimension(R.dimen.tinyTextSize); + float maxTextSize = + getResources().getDimension(R.dimen.regularTextSize); + float textSize = baseSize * 0.5f; + + paint.setTextSize( + Math.max(Math.min(textSize, maxTextSize), minTextSize)); + em = paint.getFontSpacing(); + textMargin = 0.5f * em; + + updateMaxMin(); + } + private void createColors() { if (habit != null) this.primaryColor = @@ -145,13 +212,6 @@ public class HabitStreakView extends View R.attr.highContrastReverseTextColor); } - protected void createPaints() - { - paint = new Paint(); - paint.setTextAlign(Paint.Align.CENTER); - paint.setAntiAlias(true); - } - private void drawRow(Canvas canvas, Streak streak, RectF rect) { if (maxLength == 0) return; @@ -208,75 +268,6 @@ public class HabitStreakView extends View baseSize = getResources().getDimensionPixelSize(R.dimen.baseSize); } - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - new BaseTask() - { - @Override - protected void doInBackground() - { - refreshData(); - } - }.execute(); - habit.getObservable().addListener(this); - habit.getStreaks().getObservable().addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - habit.getStreaks().getObservable().removeListener(this); - habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); - } - - @Override - protected void onDraw(Canvas canvas) - { - super.onDraw(canvas); - if (streaks.size() == 0) return; - - rect.set(0, 0, width, baseSize); - - for (Streak s : streaks) - { - drawRow(canvas, s, rect); - rect.offset(0, baseSize); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(width, height); - } - - @Override - protected void onSizeChanged(int width, - int height, - int oldWidth, - int oldHeight) - { - maxStreakCount = height / baseSize; - this.width = width; - - float minTextSize = getResources().getDimension(R.dimen.tinyTextSize); - float maxTextSize = - getResources().getDimension(R.dimen.regularTextSize); - float textSize = baseSize * 0.5f; - - paint.setTextSize( - Math.max(Math.min(textSize, maxTextSize), minTextSize)); - em = paint.getFontSpacing(); - textMargin = 0.5f * em; - - updateMaxMin(); - } - private int percentageToColor(float percentage) { if (percentage >= 1.0f) return colors[3]; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/RingView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/RingView.java index f471dfce9..2d124fe64 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/RingView.java @@ -19,47 +19,52 @@ package org.isoron.uhabits.ui.habits.show.views; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.RectF; -import android.support.annotation.Nullable; -import android.text.TextPaint; -import android.util.AttributeSet; -import android.view.View; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.InterfaceUtils; +import android.content.*; +import android.graphics.*; +import android.support.annotation.*; +import android.text.*; +import android.util.*; +import android.view.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.utils.*; + +import static org.isoron.uhabits.utils.InterfaceUtils.*; public class RingView extends View { public static final PorterDuffXfermode XFERMODE_CLEAR = - new PorterDuffXfermode(PorterDuff.Mode.CLEAR); + new PorterDuffXfermode(PorterDuff.Mode.CLEAR); private int color; + private float precision; + private float percentage; + private int diameter; + private float thickness; private RectF rect; + private TextPaint pRing; private Integer backgroundColor; + private Integer inactiveColor; private float em; + private String text; + private float textSize; + private boolean enableFontAwesome; @Nullable private Bitmap drawingCache; + private Canvas cacheCanvas; private boolean isTransparencyEnabled; @@ -71,54 +76,56 @@ public class RingView extends View percentage = 0.0f; precision = 0.01f; color = ColorUtils.getAndroidTestColor(0); - thickness = InterfaceUtils.dpToPixels(getContext(), 2); + thickness = dpToPixels(getContext(), 2); text = ""; textSize = context.getResources().getDimension(R.dimen.smallTextSize); init(); } - public RingView(Context context, AttributeSet attrs) + public RingView(Context ctx, AttributeSet attrs) { - super(context, attrs); + super(ctx, attrs); - percentage = InterfaceUtils.getFloatAttribute(context, attrs, "percentage", 0); - precision = InterfaceUtils.getFloatAttribute(context, attrs, "precision", 0.01f); + percentage = getFloatAttribute(ctx, attrs, "percentage", 0); + precision = getFloatAttribute(ctx, attrs, "precision", 0.01f); - color = InterfaceUtils.getColorAttribute(context, attrs, "color", 0); - backgroundColor = InterfaceUtils.getColorAttribute(context, attrs, "backgroundColor", null); - inactiveColor = InterfaceUtils.getColorAttribute(context, attrs, "inactiveColor", null); + color = getColorAttribute(ctx, attrs, "color", 0); + backgroundColor = + getColorAttribute(ctx, attrs, "backgroundColor", null); + inactiveColor = getColorAttribute(ctx, attrs, "inactiveColor", null); - thickness = InterfaceUtils.getFloatAttribute(context, attrs, "thickness", 0); - thickness = InterfaceUtils.dpToPixels(context, thickness); + thickness = getFloatAttribute(ctx, attrs, "thickness", 0); + thickness = dpToPixels(ctx, thickness); - float defaultTextSize = context.getResources().getDimension(R.dimen.smallTextSize); - textSize = InterfaceUtils.getFloatAttribute(context, attrs, "textSize", defaultTextSize); - textSize = InterfaceUtils.spToPixels(context, textSize); + float defaultTextSize = + ctx.getResources().getDimension(R.dimen.smallTextSize); + textSize = getFloatAttribute(ctx, attrs, "textSize", defaultTextSize); + textSize = spToPixels(ctx, textSize); + text = getAttribute(ctx, attrs, "text", ""); - text = InterfaceUtils.getAttribute(context, attrs, "text", ""); - - enableFontAwesome = InterfaceUtils.getBooleanAttribute(context, attrs, "enableFontAwesome", false); + enableFontAwesome = + getBooleanAttribute(ctx, attrs, "enableFontAwesome", false); init(); } - public void setColor(int color) + @Override + public void setBackgroundColor(int backgroundColor) { - this.color = color; + this.backgroundColor = backgroundColor; postInvalidate(); } - public void setTextSize(float textSize) + public void setColor(int color) { - this.textSize = textSize; + this.color = color; + postInvalidate(); } - @Override - public void setBackgroundColor(int backgroundColor) + public void setIsTransparencyEnabled(boolean isTransparencyEnabled) { - this.backgroundColor = backgroundColor; - postInvalidate(); + this.isTransparencyEnabled = isTransparencyEnabled; } public void setPercentage(float percentage) @@ -133,64 +140,21 @@ public class RingView extends View postInvalidate(); } - public void setThickness(float thickness) - { - this.thickness = thickness; - postInvalidate(); - } - public void setText(String text) { this.text = text; postInvalidate(); } - private void init() - { - pRing = new TextPaint(); - pRing.setAntiAlias(true); - pRing.setColor(color); - pRing.setTextAlign(Paint.Align.CENTER); - - if(backgroundColor == null) - backgroundColor = InterfaceUtils.getStyledColor(getContext(), R.attr.cardBackgroundColor); - - if(inactiveColor == null) - inactiveColor = InterfaceUtils.getStyledColor(getContext(), R.attr.highContrastTextColor); - - inactiveColor = ColorUtils.setAlpha(inactiveColor, 0.1f); - - rect = new RectF(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - diameter = Math.min(height, width); - - pRing.setTextSize(textSize); - em = pRing.measureText("M"); - - setMeasuredDimension(diameter, diameter); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) + public void setTextSize(float textSize) { - super.onSizeChanged(w, h, oldw, oldh); - - if(isTransparencyEnabled) reallocateCache(); + this.textSize = textSize; } - private void reallocateCache() + public void setThickness(float thickness) { - if (drawingCache != null) drawingCache.recycle(); - drawingCache = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888); - cacheCanvas = new Canvas(drawingCache); + this.thickness = thickness; + postInvalidate(); } @Override @@ -199,9 +163,9 @@ public class RingView extends View super.onDraw(canvas); Canvas activeCanvas; - if(isTransparencyEnabled) + if (isTransparencyEnabled) { - if(drawingCache == null) reallocateCache(); + if (drawingCache == null) reallocateCache(); activeCanvas = cacheCanvas; drawingCache.eraseColor(Color.TRANSPARENT); } @@ -220,12 +184,10 @@ public class RingView extends View pRing.setColor(inactiveColor); activeCanvas.drawArc(rect, angle - 90, 360 - angle, true, pRing); - if(thickness > 0) + if (thickness > 0) { - if(isTransparencyEnabled) - pRing.setXfermode(XFERMODE_CLEAR); - else - pRing.setColor(backgroundColor); + if (isTransparencyEnabled) pRing.setXfermode(XFERMODE_CLEAR); + else pRing.setColor(backgroundColor); rect.inset(thickness, thickness); activeCanvas.drawArc(rect, 0, 360, true, pRing); @@ -233,16 +195,63 @@ public class RingView extends View pRing.setColor(color); pRing.setTextSize(textSize); - if(enableFontAwesome) pRing.setTypeface(InterfaceUtils.getFontAwesome(getContext())); - activeCanvas.drawText(text, rect.centerX(), rect.centerY() + 0.4f * em, pRing); + if (enableFontAwesome) + pRing.setTypeface(getFontAwesome(getContext())); + activeCanvas.drawText(text, rect.centerX(), + rect.centerY() + 0.4f * em, pRing); } - if(activeCanvas != canvas) - canvas.drawBitmap(drawingCache, 0, 0, null); + if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null); } - public void setIsTransparencyEnabled(boolean isTransparencyEnabled) + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - this.isTransparencyEnabled = isTransparencyEnabled; + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + diameter = Math.min(height, width); + + pRing.setTextSize(textSize); + em = pRing.measureText("M"); + + setMeasuredDimension(diameter, diameter); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) + { + super.onSizeChanged(w, h, oldw, oldh); + + if (isTransparencyEnabled) reallocateCache(); + } + + private void init() + { + pRing = new TextPaint(); + pRing.setAntiAlias(true); + pRing.setColor(color); + pRing.setTextAlign(Paint.Align.CENTER); + + if (backgroundColor == null) backgroundColor = + InterfaceUtils.getStyledColor(getContext(), + R.attr.cardBackgroundColor); + + if (inactiveColor == null) inactiveColor = + InterfaceUtils.getStyledColor(getContext(), + R.attr.highContrastTextColor); + + inactiveColor = ColorUtils.setAlpha(inactiveColor, 0.1f); + + rect = new RectF(); + } + + private void reallocateCache() + { + if (drawingCache != null) drawingCache.recycle(); + drawingCache = + Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888); + cacheCanvas = new Canvas(drawingCache); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java index 746d06cd0..09fa9dd97 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java @@ -19,14 +19,11 @@ package org.isoron.uhabits.ui.habits.show.views; -import android.animation.ValueAnimator; -import android.content.Context; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewParent; -import android.widget.Scroller; +import android.animation.*; +import android.content.*; +import android.util.*; +import android.view.*; +import android.widget.*; public abstract class ScrollableDataView extends View implements GestureDetector.OnGestureListener, diff --git a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java index c281f23d8..24198dc7b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ui/settings/SettingsActivity.java @@ -20,6 +20,9 @@ package org.isoron.uhabits.ui.settings; import android.os.*; +import android.support.annotation.*; +import android.support.v4.app.*; +import android.view.*; import org.isoron.uhabits.*; import org.isoron.uhabits.ui.*; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java b/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java index 47a8b2587..d1893d536 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java @@ -31,7 +31,6 @@ import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.HabitWidgetView; import org.isoron.uhabits.ui.habits.show.views.RingView; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.InterfaceUtils; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java b/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java index 4198d84b1..621e00b18 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java @@ -28,7 +28,6 @@ import android.widget.TextView; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.HabitWidgetView; public class GraphWidgetView extends HabitWidgetView implements HabitDataView { diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitWidgetView.java b/app/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java similarity index 66% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitWidgetView.java rename to app/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java index 6b161f016..f7ca8131a 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java @@ -17,27 +17,26 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.drawable.InsetDrawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.RoundRectShape; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.InterfaceUtils; - -import java.util.Arrays; - -public abstract class HabitWidgetView extends FrameLayout implements HabitDataView +package org.isoron.uhabits.widgets.views; + +import android.content.*; +import android.graphics.*; +import android.graphics.drawable.*; +import android.graphics.drawable.shapes.*; +import android.support.annotation.*; +import android.util.*; +import android.view.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.utils.*; + +import java.util.*; + +public abstract class HabitWidgetView extends FrameLayout + implements HabitDataView { @Nullable protected InsetDrawable background; @@ -47,12 +46,8 @@ public abstract class HabitWidgetView extends FrameLayout implements HabitDataV @Nullable protected Habit habit; - protected ViewGroup frame; - public void setShadowAlpha(int shadowAlpha) - { - this.shadowAlpha = shadowAlpha; - } + protected ViewGroup frame; private int shadowAlpha; @@ -68,21 +63,28 @@ public abstract class HabitWidgetView extends FrameLayout implements HabitDataV init(); } - private void init() + @Override + public void setHabit(@NonNull Habit habit) { - inflate(getContext(), getInnerLayoutId(), this); - shadowAlpha = (int) (255 * InterfaceUtils.getStyledFloat(getContext(), R.attr.widgetShadowAlpha)); - rebuildBackground(); + this.habit = habit; } - protected abstract @NonNull Integer getInnerLayoutId(); + public void setShadowAlpha(int shadowAlpha) + { + this.shadowAlpha = shadowAlpha; + } + + protected abstract + @NonNull + Integer getInnerLayoutId(); protected void rebuildBackground() { Context context = getContext(); - int backgroundAlpha = - (int) (255 * InterfaceUtils.getStyledFloat(context, R.attr.widgetBackgroundAlpha)); + int backgroundAlpha = (int) (255 * + InterfaceUtils.getStyledFloat(context, + R.attr.widgetBackgroundAlpha)); int shadowRadius = (int) InterfaceUtils.dpToPixels(context, 2); int shadowOffset = (int) InterfaceUtils.dpToPixels(context, 1); @@ -98,20 +100,25 @@ public abstract class HabitWidgetView extends FrameLayout implements HabitDataV int insetLeftTop = Math.max(shadowRadius - shadowOffset, 0); int insetRightBottom = shadowRadius + shadowOffset; - background = new InsetDrawable(innerDrawable, insetLeftTop, insetLeftTop, insetRightBottom, - insetRightBottom); + background = + new InsetDrawable(innerDrawable, insetLeftTop, insetLeftTop, + insetRightBottom, insetRightBottom); backgroundPaint = innerDrawable.getPaint(); - backgroundPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, shadowColor); - backgroundPaint.setColor(InterfaceUtils.getStyledColor(context, R.attr.cardBackgroundColor)); + backgroundPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, + shadowColor); + backgroundPaint.setColor( + InterfaceUtils.getStyledColor(context, R.attr.cardBackgroundColor)); backgroundPaint.setAlpha(backgroundAlpha); frame = (ViewGroup) findViewById(R.id.frame); - if(frame != null) frame.setBackgroundDrawable(background); + if (frame != null) frame.setBackgroundDrawable(background); } - @Override - public void setHabit(@NonNull Habit habit) + private void init() { - this.habit = habit; + inflate(getContext(), getInnerLayoutId(), this); + shadowAlpha = (int) (255 * InterfaceUtils.getStyledFloat(getContext(), + R.attr.widgetShadowAlpha)); + rebuildBackground(); } } From abe6b109642ecf4f01b9fb4930690415235efd12 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 15 Jun 2016 05:32:20 -0400 Subject: [PATCH 025/184] Restore dynamic checkmark button count --- .../org/isoron/uhabits/AndroidModule.java | 23 ++-- .../ui/habits/list/ListHabitsRootView.java | 102 +++++++++--------- .../ui/habits/list/ListHabitsScreen.java | 4 +- .../list/model/HabitCardListAdapter.java | 33 +++--- .../habits/list/model/HabitCardListCache.java | 27 +++-- .../ui/habits/list/views/HabitCardView.java | 7 +- .../ui/habits/list/views/HeaderView.java | 12 +-- .../java/org/isoron/uhabits/TestModule.java | 26 ++--- 8 files changed, 103 insertions(+), 131 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/AndroidModule.java b/app/src/main/java/org/isoron/uhabits/AndroidModule.java index bd658b9f8..21f5bb008 100644 --- a/app/src/main/java/org/isoron/uhabits/AndroidModule.java +++ b/app/src/main/java/org/isoron/uhabits/AndroidModule.java @@ -19,18 +19,14 @@ package org.isoron.uhabits; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.models.ModelFactory; -import org.isoron.uhabits.models.sqlite.SQLModelFactory; -import org.isoron.uhabits.models.sqlite.SQLiteHabitList; -import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; -import org.isoron.uhabits.utils.Preferences; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.sqlite.*; +import org.isoron.uhabits.utils.*; -import javax.inject.Singleton; +import javax.inject.*; -import dagger.Module; -import dagger.Provides; +import dagger.*; /** * Module that provides dependencies when the application is running on @@ -48,13 +44,6 @@ public class AndroidModule return new CommandRunner(); } - @Provides - @Singleton - HabitCardListCache provideHabitCardListCache() - { - return new HabitCardListCache(); - } - @Provides @Singleton HabitList provideHabitList() diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java index 83c634122..0928a42dd 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java @@ -19,31 +19,28 @@ package org.isoron.uhabits.ui.habits.list; -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.content.*; +import android.content.res.*; +import android.support.annotation.*; import android.support.v7.widget.Toolbar; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.ModelObservable; -import org.isoron.uhabits.ui.BaseRootView; -import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; -import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; -import org.isoron.uhabits.ui.habits.list.model.HintList; -import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; -import org.isoron.uhabits.ui.habits.list.views.HintView; -import org.isoron.uhabits.utils.InterfaceUtils; - -import butterknife.BindView; -import butterknife.ButterKnife; +import android.view.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.*; +import org.isoron.uhabits.ui.habits.list.controllers.*; +import org.isoron.uhabits.ui.habits.list.model.*; +import org.isoron.uhabits.ui.habits.list.views.*; +import org.isoron.uhabits.utils.*; + +import butterknife.*; public class ListHabitsRootView extends BaseRootView implements ModelObservable.Listener { + public static final int MAX_CHECKMARK_COUNT = 21; + @BindView(R.id.listView) HabitCardListView listView; @@ -71,6 +68,15 @@ public class ListHabitsRootView extends BaseRootView init(); } + public static int getCheckmarkCount(View v) + { + Resources res = v.getResources(); + float labelWidth = res.getDimension(R.dimen.habitNameWidth); + float buttonWidth = res.getDimension(R.dimen.checkmarkWidth); + return Math.min(MAX_CHECKMARK_COUNT, Math.max(0, + (int) ((v.getMeasuredWidth() - labelWidth) / buttonWidth))); + } + @Override @NonNull public ProgressBar getProgressBar() @@ -80,10 +86,16 @@ public class ListHabitsRootView extends BaseRootView public boolean getShowArchived() { - if(listAdapter == null) return false; + if (listAdapter == null) return false; return listAdapter.getIncludeArchived(); } + public void setShowArchived(boolean showArchived) + { + if (listAdapter == null) return; + listAdapter.setShowArchived(showArchived); + } + @NonNull @Override public Toolbar getToolbar() @@ -103,19 +115,6 @@ public class ListHabitsRootView extends BaseRootView updateEmptyView(); } - public void setShowArchived(boolean showArchived) - { - if(listAdapter == null) return; - listAdapter.setShowArchived(showArchived); - } - - private void updateEmptyView() - { - if (listAdapter == null) return; - llEmpty.setVisibility( - listAdapter.getCount() > 0 ? View.GONE : View.VISIBLE); - } - public void setController(@Nullable ListHabitsController controller, @Nullable ListHabitsSelectionMenu menu) { @@ -139,20 +138,6 @@ public class ListHabitsRootView extends BaseRootView listAdapter.setListView(listView); } - private void init() - { - addView(inflate(getContext(), R.layout.list_habits, null)); - ButterKnife.bind(this); - - tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(getContext())); - initToolbar(); - - String hints[] = - getContext().getResources().getStringArray(R.array.hints); - HintList hintList = new HintList(hints); - hintView.setHints(hintList); - } - @Override protected void onAttachedToWindow() { @@ -170,4 +155,25 @@ public class ListHabitsRootView extends BaseRootView listAdapter.getObservable().removeListener(this); super.onDetachedFromWindow(); } + + private void init() + { + addView(inflate(getContext(), R.layout.list_habits, null)); + ButterKnife.bind(this); + + tvStarEmpty.setTypeface(InterfaceUtils.getFontAwesome(getContext())); + initToolbar(); + + String hints[] = + getContext().getResources().getStringArray(R.array.hints); + HintList hintList = new HintList(hints); + hintView.setHints(hintList); + } + + private void updateEmptyView() + { + if (listAdapter == null) return; + llEmpty.setVisibility( + listAdapter.getCount() > 0 ? View.GONE : View.VISIBLE); + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java index b617164d2..072d1ac40 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java @@ -51,6 +51,7 @@ import java.io.File; public class ListHabitsScreen extends BaseScreen { + @Nullable ListHabitsController controller; @@ -71,7 +72,8 @@ public class ListHabitsScreen extends BaseScreen setMenu(menu); setSelectionMenu(selectionMenu); - HabitCardListAdapter adapter = new HabitCardListAdapter(); + HabitCardListAdapter adapter = new HabitCardListAdapter( + ListHabitsRootView.MAX_CHECKMARK_COUNT); rootView.setListAdapter(adapter); selectionMenu.setAdapter(adapter); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java index 306678390..8f3a9d925 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java @@ -19,22 +19,15 @@ package org.isoron.uhabits.ui.habits.list.model; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; +import android.support.annotation.*; +import android.view.*; +import android.widget.*; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.ModelObservable; -import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; -import org.isoron.uhabits.ui.habits.list.views.HabitCardView; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.list.views.*; -import java.util.LinkedList; -import java.util.List; - -import javax.inject.Inject; +import java.util.*; /** * Provides data that backs a {@link HabitCardListView}. @@ -48,25 +41,25 @@ public class HabitCardListAdapter extends BaseAdapter @NonNull private ModelObservable observable; - @Inject - @NonNull - HabitCardListCache cache; - @Nullable private HabitCardListView listView; @NonNull private final LinkedList selected; - public HabitCardListAdapter() + @NonNull + private final HabitCardListCache cache; + + public HabitCardListAdapter(int checkmarkCount) { this.selected = new LinkedList<>(); this.observable = new ModelObservable(); HabitsApplication.getComponent().inject(this); + cache = new HabitCardListCache(); cache.setListener(this); - cache.setCheckmarkCount(5); // TODO: make this dynamic somehow + cache.setCheckmarkCount(checkmarkCount); } /** diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java index 874b01e46..74fd7e980 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java @@ -19,22 +19,18 @@ package org.isoron.uhabits.ui.habits.list.model; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; +import android.util.*; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.utils.*; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.*; -import javax.inject.Inject; +import javax.inject.*; /** * A HabitCardListCache fetches and keeps a cache of all the data necessary to @@ -114,7 +110,7 @@ public class HabitCardListCache implements CommandRunner.Listener public void onAttached() { -// refreshAllHabits(true); + refreshAllHabits(true); if (lastLoadTimestamp == null) refreshAllHabits(true); commandRunner.addListener(this); } @@ -129,11 +125,12 @@ public class HabitCardListCache implements CommandRunner.Listener public void onDetached() { -// commandRunner.removeListener(this); + commandRunner.removeListener(this); } public void refreshAllHabits(final boolean refreshScoresAndCheckmarks) { + Log.d("HabitCardListCache", "Refreshing all habits"); if (currentFetchTask != null) currentFetchTask.cancel(true); currentFetchTask = new RefreshAllHabitsTask(refreshScoresAndCheckmarks); currentFetchTask.execute(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java index 2c12dc181..3624160d9 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java @@ -32,10 +32,11 @@ import android.widget.TextView; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.ui.habits.list.*; import org.isoron.uhabits.ui.habits.show.views.RingView; import org.isoron.uhabits.utils.ColorUtils; -import java.util.Random; +import java.util.*; import butterknife.BindView; import butterknife.ButterKnife; @@ -81,7 +82,9 @@ public class HabitCardView extends FrameLayout public void setCheckmarkValues(int checkmarks[]) { - checkmarkPanel.setCheckmarkValues(checkmarks); + int count = ListHabitsRootView.getCheckmarkCount(this); + int visibleCheckmarks[] = Arrays.copyOfRange(checkmarks, 0, count); + checkmarkPanel.setCheckmarkValues(visibleCheckmarks); postInvalidate(); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java index 17a361087..084560c04 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HeaderView.java @@ -28,6 +28,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import org.isoron.uhabits.R; +import org.isoron.uhabits.ui.habits.list.*; import org.isoron.uhabits.utils.DateUtils; import java.util.GregorianCalendar; @@ -50,8 +51,9 @@ public class HeaderView extends LinearLayout { removeAllViews(); GregorianCalendar day = DateUtils.getStartOfTodayCalendar(); + double count = ListHabitsRootView.getCheckmarkCount(this); - for (int i = 0; i < getButtonCount(); i++) + for (int i = 0; i < count; i++) { int position = 0; @@ -66,14 +68,6 @@ public class HeaderView extends LinearLayout } } - private int getButtonCount() - { - float labelWidth = getResources().getDimension(R.dimen.habitNameWidth); - float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth); - return Math.max(0, - (int) ((getMeasuredWidth() - labelWidth) / buttonWidth)); - } - private int getCheckmarkOrder() { if (isInEditMode()) return CHECKMARK_LEFT_TO_RIGHT; diff --git a/app/src/test/java/org/isoron/uhabits/TestModule.java b/app/src/test/java/org/isoron/uhabits/TestModule.java index ad688bf1d..f49c96318 100644 --- a/app/src/test/java/org/isoron/uhabits/TestModule.java +++ b/app/src/test/java/org/isoron/uhabits/TestModule.java @@ -19,21 +19,16 @@ package org.isoron.uhabits; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.models.ModelFactory; -import org.isoron.uhabits.models.memory.MemoryHabitList; -import org.isoron.uhabits.models.memory.MemoryModelFactory; -import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; -import org.isoron.uhabits.utils.Preferences; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.models.memory.*; +import org.isoron.uhabits.utils.*; -import javax.inject.Singleton; +import javax.inject.*; -import dagger.Module; -import dagger.Provides; +import dagger.*; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; @Module public class TestModule @@ -52,13 +47,6 @@ public class TestModule return mock(CommandRunner.class); } - @Singleton - @Provides - HabitCardListCache provideHabitCardListCache() - { - return mock(HabitCardListCache.class); - } - @Singleton @Provides HabitList provideHabitList() From fee3137a6fef2425fde713b94c56b75c965610fd Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 15 Jun 2016 05:39:13 -0400 Subject: [PATCH 026/184] Move ripple to correct place --- .../list/controllers/HabitCardController.java | 2 +- .../ui/habits/list/views/HabitCardView.java | 59 +++++++++++-------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java index bdc65acf5..f2f2b662b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardController.java @@ -41,7 +41,7 @@ public class HabitCardController implements HabitCardView.Controller @Override public void onToggle(Habit habit, long timestamp) { - if (view != null) view.triggerRipple(0, 0); + if (view != null) view.triggerRipple(timestamp); if (listener != null) listener.onToggle(habit, timestamp); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java index 3624160d9..6a60ff30b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/views/HabitCardView.java @@ -19,30 +19,25 @@ package org.isoron.uhabits.ui.habits.list.views; -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.util.AttributeSet; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TextView; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; +import android.annotation.*; +import android.content.*; +import android.graphics.drawable.*; +import android.os.*; +import android.util.*; +import android.view.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; import org.isoron.uhabits.ui.habits.list.*; -import org.isoron.uhabits.ui.habits.show.views.RingView; -import org.isoron.uhabits.utils.ColorUtils; +import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.utils.*; import java.util.*; -import butterknife.BindView; -import butterknife.ButterKnife; +import butterknife.*; -import static org.isoron.uhabits.utils.InterfaceUtils.getStyledColor; -import static org.isoron.uhabits.utils.InterfaceUtils.getStyledDrawable; +import static org.isoron.uhabits.utils.InterfaceUtils.*; public class HabitCardView extends FrameLayout { @@ -125,14 +120,16 @@ public class HabitCardView extends FrameLayout updateBackground(isSelected); } - public void triggerRipple(final float x, final float y) + public void triggerRipple(long timestamp) { - final Drawable background = innerFrame.getBackground(); - if (android.os.Build.VERSION.SDK_INT >= 21) background.setHotspot(x, y); - background.setState(new int[]{ - android.R.attr.state_pressed, android.R.attr.state_enabled - }); - new Handler().postDelayed(() -> background.setState(new int[]{}), 25); + long today = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; + int offset = (int) ((today - timestamp) / day); + CheckmarkButtonView button = checkmarkPanel.getButton(offset); + + float y = button.getHeight() / 2.0f; + float x = checkmarkPanel.getX() + button.getX() + button.getWidth() / 2; + triggerRipple(x, y); } private int getActiveColor(Habit habit) @@ -193,6 +190,16 @@ public class HabitCardView extends FrameLayout checkmarkPanel.setCheckmarkValues(values); } + private void triggerRipple(final float x, final float y) + { + final Drawable background = innerFrame.getBackground(); + if (android.os.Build.VERSION.SDK_INT >= 21) background.setHotspot(x, y); + background.setState(new int[]{ + android.R.attr.state_pressed, android.R.attr.state_enabled + }); + new Handler().postDelayed(() -> background.setState(new int[]{}), 25); + } + private void updateBackground(boolean isSelected) { if (android.os.Build.VERSION.SDK_INT >= 21) From ec0e8ac24ca8d9d52d5978f09bf1ebd3ac35ac5a Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 15 Jun 2016 09:48:01 -0400 Subject: [PATCH 027/184] Cancel selection with back button correctly --- .../ui/habits/list/ListHabitsRootView.java | 4 +- .../ui/habits/list/ListHabitsScreen.java | 2 +- .../habits/list/ListHabitsSelectionMenu.java | 71 ++++++++++--------- .../controllers/HabitCardListController.java | 49 +++++++------ 4 files changed, 68 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java index 0928a42dd..a950b363c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java @@ -119,13 +119,15 @@ public class ListHabitsRootView extends BaseRootView @Nullable ListHabitsSelectionMenu menu) { listView.setController(null); - if (controller == null || listAdapter == null) return; + if (controller == null || menu == null || listAdapter == null) return; HabitCardListController listController = new HabitCardListController(listAdapter, listView); + listController.setHabitListener(controller); listController.setSelectionListener(menu); listView.setController(listController); + menu.setListController(listController); } public void setListAdapter(@NonNull HabitCardListAdapter listAdapter) diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java index 072d1ac40..1a189464b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsScreen.java @@ -75,7 +75,7 @@ public class ListHabitsScreen extends BaseScreen HabitCardListAdapter adapter = new HabitCardListAdapter( ListHabitsRootView.MAX_CHECKMARK_COUNT); rootView.setListAdapter(adapter); - selectionMenu.setAdapter(adapter); + selectionMenu.setListAdapter(adapter); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java index 27ae3c4ef..540f71e90 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsSelectionMenu.java @@ -19,26 +19,19 @@ package org.isoron.uhabits.ui.habits.list; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.Menu; -import android.view.MenuItem; - -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.ArchiveHabitsCommand; -import org.isoron.uhabits.commands.ChangeHabitColorCommand; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.commands.DeleteHabitsCommand; -import org.isoron.uhabits.commands.UnarchiveHabitsCommand; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.BaseSelectionMenu; -import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; -import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; - -import java.util.List; - -import javax.inject.Inject; +import android.support.annotation.*; +import android.view.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.*; +import org.isoron.uhabits.ui.habits.list.controllers.*; +import org.isoron.uhabits.ui.habits.list.model.*; + +import java.util.*; + +import javax.inject.*; public class ListHabitsSelectionMenu extends BaseSelectionMenu implements HabitCardListController.SelectionListener @@ -50,7 +43,10 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu CommandRunner commandRunner; @Nullable - private HabitCardListAdapter adapter; + private HabitCardListAdapter listAdapter; + + @Nullable + private HabitCardListController listController; public ListHabitsSelectionMenu(@NonNull ListHabitsScreen screen) { @@ -61,16 +57,16 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu @Override public void onFinish() { - if (adapter != null) adapter.clearSelection(); + if (listController != null) listController.onSelectionFinished(); super.onFinish(); } @Override public boolean onItemClicked(@NonNull MenuItem item) { - if (adapter == null) return false; + if (listAdapter == null) return false; - List selected = adapter.getSelected(); + List selected = listAdapter.getSelected(); if (selected.isEmpty()) return false; Habit firstHabit = selected.get(0); @@ -108,8 +104,8 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu @Override public boolean onPrepare(@NonNull Menu menu) { - if (adapter == null) return false; - List selected = adapter.getSelected(); + if (listAdapter == null) return false; + List selected = listAdapter.getSelected(); boolean showEdit = (selected.size() == 1); boolean showArchive = true; @@ -153,10 +149,21 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu screen.startSelection(); } - public void setAdapter(@Nullable HabitCardListAdapter adapter) + public void setListAdapter(@Nullable HabitCardListAdapter listAdapter) + { + if (listAdapter == null) return; + this.listAdapter = listAdapter; + } + + public void setListController(HabitCardListController listController) + { + this.listController = listController; + } + + @Override + protected int getResourceId() { - if (adapter == null) return; - this.adapter = adapter; + return R.menu.list_habits_selection; } private void archive(@NonNull List selected) @@ -177,12 +184,6 @@ public class ListHabitsSelectionMenu extends BaseSelectionMenu screen.showEditHabitScreen(firstHabit); } - @Override - protected int getResourceId() - { - return R.menu.list_habits_selection; - } - private void showColorPicker(@NonNull List selected, @NonNull Habit firstHabit) { diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java index ebb69d60c..192d92f21 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/controllers/HabitCardListController.java @@ -19,14 +19,13 @@ package org.isoron.uhabits.ui.habits.list.controllers; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.annotation.*; -import com.mobeta.android.dslv.DragSortListView; +import com.mobeta.android.dslv.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.habits.list.model.HabitCardListAdapter; -import org.isoron.uhabits.ui.habits.list.views.HabitCardListView; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.list.model.*; +import org.isoron.uhabits.ui.habits.list.views.*; /** * Controller responsible for receiving and processing the events generated by a @@ -131,6 +130,16 @@ public class HabitCardListController implements DragSortListView.DropListener, activeMode.onItemLongClick(position); } + /** + * Called when the selection operation is cancelled externally, by something + * other than this controller. This happens, for example, when the user + * presses the back button. + */ + public void onSelectionFinished() + { + cancelSelection(); + } + /** * Called when the user wants to toggle a checkmark. * @@ -153,7 +162,6 @@ public class HabitCardListController implements DragSortListView.DropListener, this.selectionListener = listener; } - /** * Called when the user starts dragging an item. * @@ -165,6 +173,17 @@ public class HabitCardListController implements DragSortListView.DropListener, activeMode.startDrag(position); } + /** + * Selects or deselects the item at a given position + * + * @param position the position of the item to be selected/deselected + */ + protected void toggleSelection(int position) + { + adapter.toggleSelection(position); + activeMode = adapter.isSelectionEmpty() ? NORMAL_MODE : SELECTION_MODE; + } + /** * Marks all items as not selected and finishes the selection operation. */ @@ -177,17 +196,6 @@ public class HabitCardListController implements DragSortListView.DropListener, if (selectionListener != null) selectionListener.onSelectionFinish(); } - /** - * Selects or deselects the item at a given position - * - * @param position the position of the item to be selected/deselected - */ - protected void toggleSelection(int position) - { - adapter.toggleSelection(position); - activeMode = adapter.isSelectionEmpty() ? NORMAL_MODE : SELECTION_MODE; - } - public interface HabitListener extends CheckmarkButtonController.Listener { /** @@ -306,12 +314,11 @@ public class HabitCardListController implements DragSortListView.DropListener, protected void notifyListener() { - if(selectionListener == null) return; + if (selectionListener == null) return; if (activeMode == SELECTION_MODE) selectionListener.onSelectionChange(); - else - selectionListener.onSelectionFinish(); + else selectionListener.onSelectionFinish(); } } } From 5d8a348aafa6b2f144546e5f9c9ff995d86b41e6 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 15 Jun 2016 09:54:39 -0400 Subject: [PATCH 028/184] Hide empty message at startup --- .../uhabits/ui/habits/list/ListHabitsRootView.java | 10 +--------- app/src/main/res/layout/list_habits.xml | 3 ++- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java index a950b363c..058b8503a 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsRootView.java @@ -84,12 +84,6 @@ public class ListHabitsRootView extends BaseRootView return progressBar; } - public boolean getShowArchived() - { - if (listAdapter == null) return false; - return listAdapter.getIncludeArchived(); - } - public void setShowArchived(boolean showArchived) { if (listAdapter == null) return; @@ -144,9 +138,6 @@ public class ListHabitsRootView extends BaseRootView protected void onAttachedToWindow() { super.onAttachedToWindow(); - - updateEmptyView(); - if (listAdapter != null) listAdapter.getObservable().addListener(this); } @@ -175,6 +166,7 @@ public class ListHabitsRootView extends BaseRootView private void updateEmptyView() { if (listAdapter == null) return; + llEmpty.setVisibility( listAdapter.getCount() > 0 ? View.GONE : View.VISIBLE); } diff --git a/app/src/main/res/layout/list_habits.xml b/app/src/main/res/layout/list_habits.xml index a9e71fa69..8e4781399 100644 --- a/app/src/main/res/layout/list_habits.xml +++ b/app/src/main/res/layout/list_habits.xml @@ -55,7 +55,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" - android:orientation="vertical"> + android:orientation="vertical" + android:visibility="gone"> Date: Wed, 15 Jun 2016 10:12:33 -0400 Subject: [PATCH 029/184] Fix reordering --- .../org/isoron/uhabits/models/sqlite/SQLiteHabitList.java | 2 ++ .../uhabits/ui/habits/list/model/HabitCardListAdapter.java | 2 -- .../uhabits/ui/habits/list/model/HabitCardListCache.java | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java index 9abd9213b..18b44421b 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java @@ -205,6 +205,8 @@ public class SQLiteHabitList extends HabitList record.save(); update(from); + + getObservable().notifyListeners(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java index 8f3a9d925..1d2750316 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListAdapter.java @@ -178,8 +178,6 @@ public class HabitCardListAdapter extends BaseAdapter public void reorder(int from, int to) { cache.reorder(from, to); - cache.refreshAllHabits(false); - notifyDataSetChanged(); } /** diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java index 74fd7e980..2cc1a108b 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/model/HabitCardListCache.java @@ -144,12 +144,9 @@ public class HabitCardListCache implements CommandRunner.Listener public void reorder(int from, int to) { Habit fromHabit = data.habitsList.get(from); - Habit toHabit = data.habitsList.get(to); - data.habitsList.remove(from); data.habitsList.add(to, fromHabit); - - allHabits.reorder(fromHabit, toHabit); + if(listener != null) listener.onCacheRefresh(); } public void setCheckmarkCount(int checkmarkCount) From efc7b2cebb4f63c8099f054fb371859ffd46b9ca Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Thu, 16 Jun 2016 13:51:36 -0400 Subject: [PATCH 030/184] Replace ActiveAndroid queries with raw SQLite queries --- app/build.gradle | 1 + .../org/isoron/uhabits/HabitsApplication.java | 18 ++-- .../models/sqlite/SQLiteCheckmarkList.java | 58 +++++++--- .../models/sqlite/SQLiteHabitList.java | 51 +++++---- .../models/sqlite/SQLiteRepetitionList.java | 78 ++++++++++---- .../models/sqlite/SQLiteScoreList.java | 101 ++++++++++++------ .../uhabits/models/sqlite/SQLiteUtils.java | 84 +++++++++++++++ .../sqlite/records/CheckmarkRecord.java | 11 +- .../models/sqlite/records/HabitRecord.java | 45 +++++++- .../sqlite/records/RepetitionRecord.java | 14 ++- .../models/sqlite/records/SQLiteRecord.java | 27 +++++ .../models/sqlite/records/ScoreRecord.java | 14 ++- .../ui/habits/list/ListHabitsController.java | 40 ++++--- 13 files changed, 410 insertions(+), 132 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteUtils.java create mode 100644 app/src/main/java/org/isoron/uhabits/models/sqlite/records/SQLiteRecord.java diff --git a/app/build.gradle b/app/build.gradle index f224ae953..77ef7c0b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -58,6 +58,7 @@ dependencies { compile 'org.apmem.tools:layouts:1.10@aar' compile 'com.opencsv:opencsv:3.7' compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT' + compile 'org.jetbrains:annotations-java5:15.0' compile 'com.jakewharton:butterknife:8.0.1' apt 'com.jakewharton:butterknife-compiler:8.0.1' diff --git a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java index 34f72d2fe..72e3c38c3 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitsApplication.java +++ b/app/src/main/java/org/isoron/uhabits/HabitsApplication.java @@ -19,18 +19,18 @@ package org.isoron.uhabits; -import android.app.Application; -import android.content.Context; -import android.support.annotation.Nullable; +import android.app.*; +import android.content.*; +import android.support.annotation.*; -import com.activeandroid.ActiveAndroid; +import com.activeandroid.*; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.utils.DatabaseUtils; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; -import java.io.File; +import java.io.*; -import javax.inject.Inject; +import javax.inject.*; /** * The Android application for Loop Habit Tracker. @@ -92,7 +92,7 @@ public class HabitsApplication extends Application { if (context != null) context .getClassLoader() - .loadClass("org.isoron.uhabits.unit.models.HabitTest"); + .loadClass("org.isoron.uhabits.BaseAndroidTest"); return true; } catch (final Exception e) 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 d83808c8e..f33a3b277 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 @@ -21,13 +21,14 @@ package org.isoron.uhabits.models.sqlite; import android.database.sqlite.*; import android.support.annotation.*; +import android.support.annotation.Nullable; import com.activeandroid.*; 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.*; @@ -36,14 +37,23 @@ import java.util.*; */ public class SQLiteCheckmarkList extends CheckmarkList { + @Nullable + private HabitRecord habitRecord; + + @NonNull + private final SQLiteUtils sqlite; + public SQLiteCheckmarkList(Habit habit) { super(habit); + sqlite = new SQLiteUtils<>(CheckmarkRecord.class); } @Override public void add(List checkmarks) { + check(habit.getId()); + String query = "insert into Checkmarks(habit, timestamp, value) values (?,?,?)"; @@ -69,16 +79,26 @@ public class SQLiteCheckmarkList extends CheckmarkList } } + @NonNull @Override public List getByInterval(long fromTimestamp, long toTimestamp) { + check(habit.getId()); computeAll(); - List records = select() - .and("timestamp >= ?", fromTimestamp) - .and("timestamp <= ?", toTimestamp) - .execute(); + String query = "select habit, timestamp, value " + + "from checkmarks " + + "where habit = ? and timestamp >= ? and timestamp <= ? " + + "order by timestamp desc"; + + String params[] = { + Long.toString(habit.getId()), + Long.toString(fromTimestamp), + Long.toString(toTimestamp) + }; + List records = sqlite.query(query, params); + for (CheckmarkRecord record : records) record.habit = habitRecord; return toCheckmarks(records); } @@ -98,19 +118,31 @@ public class SQLiteCheckmarkList extends CheckmarkList @Nullable protected Checkmark getNewestComputed() { - CheckmarkRecord record = select().limit(1).executeSingle(); + check(habit.getId()); + + String query = "select habit, timestamp, value " + + "from checkmarks " + + "where habit = ? " + + "order by timestamp desc " + + "limit 1"; + + String params[] = { Long.toString(habit.getId()) }; + + CheckmarkRecord record = sqlite.querySingle(query, params); if (record == null) return null; + record.habit = habitRecord; return record.toCheckmark(); } - @NonNull - private From select() + @Contract("null -> fail") + private void check(Long id) { - return new Select() - .from(CheckmarkRecord.class) - .where("habit = ?", habit.getId()) - .and("timestamp <= ?", DateUtils.getStartOfToday()) - .orderBy("timestamp desc"); + if (id == null) throw new RuntimeException("habit is not saved"); + + if (habitRecord != null) return; + + habitRecord = HabitRecord.get(id); + if (habitRecord == null) throw new RuntimeException("habit not found"); } @NonNull diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java index 18b44421b..4a366dac4 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java @@ -19,8 +19,10 @@ package org.isoron.uhabits.models.sqlite; +import android.database.sqlite.*; import android.support.annotation.*; +import com.activeandroid.*; import com.activeandroid.query.*; import org.isoron.uhabits.models.*; @@ -37,9 +39,13 @@ public class SQLiteHabitList extends HabitList private HashMap cache; + private final SQLiteUtils sqlite; + + private SQLiteHabitList() { cache = new HashMap<>(); + sqlite = new SQLiteUtils<>(HabitRecord.class); } /** @@ -77,13 +83,18 @@ public class SQLiteHabitList extends HabitList @Override public int countActive() { - return select().count(); + SQLiteDatabase db = Cache.openDatabase(); + SQLiteStatement st = db.compileStatement( + "select count(*) from habits where archived = 0"); + return (int) st.simpleQueryForLong(); } @Override public int countWithArchived() { - return selectWithArchived().count(); + SQLiteDatabase db = Cache.openDatabase(); + SQLiteStatement st = db.compileStatement("select count(*) from habits"); + return (int) st.simpleQueryForLong(); } @Override @@ -91,8 +102,17 @@ public class SQLiteHabitList extends HabitList public List getAll(boolean includeArchive) { List recordList; - if (includeArchive) recordList = selectWithArchived().execute(); - else recordList = select().execute(); + if (includeArchive) + { + String query = HabitRecord.SELECT + "order by position"; + recordList = sqlite.query(query, null); + } + else + { + String query = HabitRecord.SELECT + "where archived = 0 " + + "order by position"; + recordList = sqlite.query(query, null); + } List habits = new LinkedList<>(); for (HabitRecord record : recordList) @@ -127,11 +147,10 @@ public class SQLiteHabitList extends HabitList @Nullable public Habit getByPosition(int position) { - HabitRecord record = selectWithArchived() - .where("position = ?", position) - .executeSingle(); - - if(record != null) return getById(record.getId()); + String query = HabitRecord.SELECT + "where position = ? limit 1"; + String params[] = { Integer.toString(position) }; + HabitRecord record = sqlite.querySingle(query, params); + if (record != null) return getById(record.getId()); return null; } @@ -222,18 +241,4 @@ public class SQLiteHabitList extends HabitList } } - @NonNull - private From select() - { - return new Select() - .from(HabitRecord.class) - .where("archived = 0") - .orderBy("position"); - } - - @NonNull - private From selectWithArchived() - { - return new Select().from(HabitRecord.class).orderBy("position"); - } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java index 620e9816d..595caf444 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java @@ -20,12 +20,13 @@ package org.isoron.uhabits.models.sqlite; import android.support.annotation.*; +import android.support.annotation.Nullable; 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.*; @@ -34,9 +35,15 @@ import java.util.*; */ public class SQLiteRepetitionList extends RepetitionList { + private final SQLiteUtils sqlite; + + @Nullable + private HabitRecord habitRecord; + public SQLiteRepetitionList(@NonNull Habit habit) { super(habit); + sqlite = new SQLiteUtils<>(RepetitionRecord.class); } /** @@ -59,25 +66,56 @@ public class SQLiteRepetitionList extends RepetitionList @Override public List getByInterval(long timeFrom, long timeTo) { - return toRepetitions(selectFromTo(timeFrom, timeTo).execute()); + check(habit.getId()); + String query = "select habit, timestamp " + + "from Repetitions " + + "where habit = ? and timestamp >= ? and timestamp <= ? " + + "order by timestamp"; + + String params[] = { + Long.toString(habit.getId()), + Long.toString(timeFrom), + Long.toString(timeTo) + }; + + List records = sqlite.query(query, params); + return toRepetitions(records); } @Override @Nullable public Repetition getByTimestamp(long timestamp) { - RepetitionRecord record = - select().where("timestamp = ?", timestamp).executeSingle(); + check(habit.getId()); + String query = "select habit, timestamp " + + "from Repetitions " + + "where habit = ? and timestamp = ? " + + "limit 1"; + + String params[] = + { Long.toString(habit.getId()), Long.toString(timestamp) }; + RepetitionRecord record = sqlite.querySingle(query, params); if (record == null) return null; + record.habit = habitRecord; return record.toRepetition(); } @Override public Repetition getOldest() { - RepetitionRecord record = select().limit(1).executeSingle(); + check(habit.getId()); + String query = "select habit, timestamp " + + "from Repetitions " + + "where habit = ? " + + "order by timestamp asc " + + "limit 1"; + + String params[] = { Long.toString(habit.getId()) }; + + RepetitionRecord record = sqlite.querySingle(query, params); if (record == null) return null; + record.habit = habitRecord; return record.toRepetition(); } @@ -93,33 +131,29 @@ public class SQLiteRepetitionList extends RepetitionList observable.notifyListeners(); } - @NonNull - private From select() + @Contract("null -> fail") + private void check(Long id) { - return new Select() - .from(RepetitionRecord.class) - .where("habit = ?", habit.getId()) - .and("timestamp <= ?", DateUtils.getStartOfToday()) - .orderBy("timestamp"); - } + if (id == null) throw new RuntimeException("habit is not saved"); - @NonNull - private From selectFromTo(long timeFrom, long timeTo) - { - return select() - .and("timestamp >= ?", timeFrom) - .and("timestamp <= ?", timeTo); + if (habitRecord != null) return; + + habitRecord = HabitRecord.get(id); + if (habitRecord == null) throw new RuntimeException("habit not found"); } @NonNull private List toRepetitions( - @Nullable List records) + @NonNull List records) { - List reps = new LinkedList<>(); - if (records == null) return reps; + check(habit.getId()); + List reps = new LinkedList<>(); for (RepetitionRecord record : records) + { + record.habit = habitRecord; reps.add(record.toRepetition()); + } return reps; } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java index 309dd1865..e20986134 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java @@ -21,12 +21,14 @@ package org.isoron.uhabits.models.sqlite; import android.database.sqlite.*; import android.support.annotation.*; +import android.support.annotation.Nullable; import com.activeandroid.*; import com.activeandroid.query.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.records.*; +import org.jetbrains.annotations.*; import java.util.*; @@ -35,6 +37,12 @@ import java.util.*; */ public class SQLiteScoreList extends ScoreList { + @Nullable + private HabitRecord habitRecord; + + @NonNull + private final SQLiteUtils sqlite; + /** * Constructs a new ScoreList associated with the given habit. * @@ -43,36 +51,13 @@ public class SQLiteScoreList extends ScoreList public SQLiteScoreList(@NonNull Habit habit) { super(habit); - } - - @Override - @NonNull - public List getAll() - { - computeAll(); - - List records = select().execute(); - List scores = new LinkedList<>(); - - for (ScoreRecord rec : records) - scores.add(rec.toScore()); - - return scores; - } - - @Override - public void invalidateNewerThan(long timestamp) - { - new Delete() - .from(ScoreRecord.class) - .where("habit = ?", habit.getId()) - .and("timestamp >= ?", timestamp) - .execute(); + sqlite = new SQLiteUtils<>(ScoreRecord.class); } @Override public void add(List scores) { + check(habit.getId()); String query = "insert into Score(habit, timestamp, score) values (?,?,?)"; @@ -99,33 +84,83 @@ public class SQLiteScoreList extends ScoreList } } + @Override + @NonNull + public List getAll() + { + check(habit.getId()); + computeAll(); + + String query = "select habit, timestamp, score from Score " + + "where habit = ? order by timestamp desc"; + + String params[] = {Long.toString(habit.getId())}; + + List records = sqlite.query(query, params); + for (ScoreRecord record : records) record.habit = habitRecord; + + List scores = new LinkedList<>(); + for (ScoreRecord rec : records) + scores.add(rec.toScore()); + + return scores; + } + @Override @Nullable public Score getByTimestamp(long timestamp) { + check(habit.getId()); computeAll(); - ScoreRecord record = - select().where("timestamp = ?", timestamp).executeSingle(); + String query = "select habit, timestamp, score from Score " + + "where habit = ? and timestamp = ? " + + "order by timestamp desc"; + + String params[] = + {Long.toString(habit.getId()), Long.toString(timestamp)}; + ScoreRecord record = sqlite.querySingle(query, params); if (record == null) return null; + record.habit = habitRecord; return record.toScore(); } + @Override + public void invalidateNewerThan(long timestamp) + { + new Delete() + .from(ScoreRecord.class) + .where("habit = ?", habit.getId()) + .and("timestamp >= ?", timestamp) + .execute(); + } + @Nullable @Override protected Score getNewestComputed() { - ScoreRecord record = select().limit(1).executeSingle(); + check(habit.getId()); + String query = "select habit, timestamp, score from Score " + + "where habit = ? order by timestamp desc " + + "limit 1"; + + String params[] = {Long.toString(habit.getId())}; + + ScoreRecord record = sqlite.querySingle(query, params); if (record == null) return null; + record.habit = habitRecord; return record.toScore(); } - private From select() + @Contract("null -> fail") + private void check(Long id) { - return new Select() - .from(ScoreRecord.class) - .where("habit = ?", habit.getId()) - .orderBy("timestamp desc"); + if (id == null) throw new RuntimeException("habit is not saved"); + + if(habitRecord != null) return; + + habitRecord = HabitRecord.get(id); + if (habitRecord == null) throw new RuntimeException("habit not found"); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteUtils.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteUtils.java new file mode 100644 index 000000000..616b0d12f --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteUtils.java @@ -0,0 +1,84 @@ +/* + * 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; + +import android.database.*; +import android.database.sqlite.*; +import android.support.annotation.*; + +import com.activeandroid.*; + +import org.isoron.uhabits.models.sqlite.records.*; + +import java.util.*; + +public class SQLiteUtils +{ + private Class klass; + + public SQLiteUtils(Class klass) + { + this.klass = klass; + } + + @NonNull + public List query(String query, String params[]) + { + SQLiteDatabase db = Cache.openDatabase(); + try (Cursor c = db.rawQuery(query, params)) + { + return cursorToMultipleRecords(c); + } + } + + @Nullable + public T querySingle(String query, String params[]) + { + SQLiteDatabase db = Cache.openDatabase(); + try(Cursor c = db.rawQuery(query, params)) + { + if (!c.moveToNext()) return null; + return cursorToSingleRecord(c); + } + } + + @NonNull + private List cursorToMultipleRecords(Cursor c) + { + List records = new LinkedList<>(); + while (c.moveToNext()) records.add(cursorToSingleRecord(c)); + return records; + } + + @NonNull + private T cursorToSingleRecord(Cursor c) + { + try + { + T record = (T) klass.newInstance(); + record.copyFrom(c); + return record; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java index f3512bd40..3e5f060b9 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java @@ -19,6 +19,8 @@ package org.isoron.uhabits.models.sqlite.records; +import android.database.*; + import com.activeandroid.*; import com.activeandroid.annotation.*; @@ -29,7 +31,7 @@ import org.isoron.uhabits.models.sqlite.*; * The SQLite database record corresponding to a {@link Checkmark}. */ @Table(name = "Checkmarks") -public class CheckmarkRecord extends Model +public class CheckmarkRecord extends Model implements SQLiteRecord { /** * The habit to which this checkmark belongs. @@ -52,6 +54,13 @@ public class CheckmarkRecord extends Model @Column(name = "value") public Integer value; + @Override + public void copyFrom(Cursor c) + { + timestamp = c.getLong(1); + value = c.getInt(2); + } + public Checkmark toCheckmark() { SQLiteHabitList habitList = SQLiteHabitList.getInstance(); diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java index e4c2e04d2..3d6c7553f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java @@ -20,6 +20,7 @@ package org.isoron.uhabits.models.sqlite.records; import android.annotation.*; +import android.database.*; import android.support.annotation.*; import com.activeandroid.*; @@ -28,17 +29,24 @@ import com.activeandroid.query.*; import com.activeandroid.util.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.utils.*; +import org.isoron.uhabits.utils.DatabaseUtils; + +import java.lang.reflect.*; /** * The SQLite database record corresponding to a {@link Habit}. */ @Table(name = "Habits") -public class HabitRecord extends Model +public class HabitRecord extends Model implements SQLiteRecord { public static final String HABIT_URI_FORMAT = "content://org.isoron.uhabits/habit/%d"; + public static String SELECT = + "select id, color, description, freq_den, freq_num, " + + "name, position, reminder_hour, reminder_min, " + + "highlight, archived, reminder_days from habits "; + @Column(name = "name") public String name; @@ -147,6 +155,23 @@ public class HabitRecord extends Model this.archived = model.getArchived(); } + @Override + public void copyFrom(Cursor c) + { + setId(c.getLong(0)); + color = c.getInt(1); + description = c.getString(2); + freqDen = c.getInt(3); + freqNum = c.getInt(4); + name = c.getString(5); + position = c.getInt(6); + reminderHour = c.getInt(7); + reminderMin = c.getInt(8); + highlight = c.getInt(9); + archived = c.getInt(10); + reminderDays = c.getInt(11); + } + public void copyTo(Habit habit) { habit.setName(this.name); @@ -172,4 +197,20 @@ public class HabitRecord extends Model save(); updateId(getId(), id); } + + private void setId(Long id) + { + // HACK: The id field is declared private by ActiveAndroid and + // there are no setters. (WTF?) + try + { + Field f = (Model.class).getDeclaredField("mId"); + f.setAccessible(true); + f.set(this, id); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java index ffc524701..d2a7667ec 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java @@ -19,6 +19,8 @@ package org.isoron.uhabits.models.sqlite.records; +import android.database.*; + import com.activeandroid.*; import com.activeandroid.annotation.*; @@ -29,7 +31,7 @@ import org.isoron.uhabits.models.sqlite.*; * The SQLite database record corresponding to a {@link Repetition}. */ @Table(name = "Repetitions") -public class RepetitionRecord extends Model +public class RepetitionRecord extends Model implements SQLiteRecord { @Column(name = "habit") public HabitRecord habit; @@ -37,15 +39,21 @@ public class RepetitionRecord extends Model @Column(name = "timestamp") public Long timestamp; + public static RepetitionRecord get(Long id) + { + return RepetitionRecord.load(RepetitionRecord.class, id); + } + public void copyFrom(Repetition repetition) { habit = HabitRecord.get(repetition.getHabit().getId()); timestamp = repetition.getTimestamp(); } - public static RepetitionRecord get(Long id) + @Override + public void copyFrom(Cursor c) { - return RepetitionRecord.load(RepetitionRecord.class, id); + timestamp = c.getLong(1); } public Repetition toRepetition() diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/SQLiteRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/SQLiteRecord.java new file mode 100644 index 000000000..1991b1276 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/SQLiteRecord.java @@ -0,0 +1,27 @@ +/* + * 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.records; + +import android.database.*; + +public interface SQLiteRecord +{ + void copyFrom(Cursor c); +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java index 2fd2b91b0..24bc65509 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java @@ -19,6 +19,8 @@ package org.isoron.uhabits.models.sqlite.records; +import android.database.*; + import com.activeandroid.*; import com.activeandroid.annotation.*; @@ -29,11 +31,8 @@ import org.isoron.uhabits.models.sqlite.*; * The SQLite database record corresponding to a Score. */ @Table(name = "Score") -public class ScoreRecord extends Model +public class ScoreRecord extends Model implements SQLiteRecord { - /** - * Habit to which this score belongs to. - */ @Column(name = "habit") public HabitRecord habit; @@ -50,6 +49,13 @@ public class ScoreRecord extends Model @Column(name = "score") public Integer score; + @Override + public void copyFrom(Cursor c) + { + timestamp = c.getLong(1); + score = c.getInt(2); + } + /** * Constructs and returns a {@link Score} based on this record's data. * diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java index 72c4a867b..6af715be0 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/list/ListHabitsController.java @@ -19,26 +19,20 @@ package org.isoron.uhabits.ui.habits.list; -import android.support.annotation.NonNull; - -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.tasks.ExportCSVTask; -import org.isoron.uhabits.tasks.ExportDBTask; -import org.isoron.uhabits.tasks.ImportDataTask; -import org.isoron.uhabits.ui.BaseSystem; -import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.Preferences; - -import java.io.File; -import java.io.IOException; - -import javax.inject.Inject; +import android.os.*; +import android.support.annotation.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.ui.*; +import org.isoron.uhabits.ui.habits.list.controllers.*; +import org.isoron.uhabits.utils.*; + +import java.io.*; + +import javax.inject.*; public class ListHabitsController implements ImportDataTask.Listener, HabitCardListController.HabitListener @@ -168,8 +162,10 @@ public class ListHabitsController prefs.updateLastAppVersion(); if (prefs.isFirstRun()) onFirstRun(); - system.updateWidgets(); - system.scheduleReminders(); + new Handler().postDelayed(() -> { + system.updateWidgets(); + system.scheduleReminders(); + }, 1000); } @Override From b13f2b4228b7c600605890522ad951d55958907a Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Thu, 16 Jun 2016 15:19:02 -0400 Subject: [PATCH 031/184] Create a class for Reminders --- .../org/isoron/uhabits/espresso/MainTest.java | 309 ++++++++---------- .../org/isoron/uhabits/io/ImportTest.java | 50 ++- .../sqlite/SQLiteCheckmarkListTest.java | 6 +- .../uhabits/HabitBroadcastReceiver.java | 9 +- .../isoron/uhabits/io/RewireDBImporter.java | 118 ++++--- .../org/isoron/uhabits/models/Checkmark.java | 10 +- .../isoron/uhabits/models/CheckmarkList.java | 2 +- .../java/org/isoron/uhabits/models/Habit.java | 89 +---- .../org/isoron/uhabits/models/Reminder.java | 58 ++++ .../sqlite/records/CheckmarkRecord.java | 2 +- .../models/sqlite/records/HabitRecord.java | 24 +- .../ui/habits/edit/BaseDialogFragment.java | 108 +++--- .../ui/habits/edit/BaseDialogHelper.java | 55 ++-- .../ui/habits/show/ShowHabitHelper.java | 19 +- .../isoron/uhabits/utils/ReminderUtils.java | 40 +-- .../java/org/isoron/uhabits/BaseUnitTest.java | 16 +- .../isoron/uhabits/models/HabitListTest.java | 16 +- .../org/isoron/uhabits/models/HabitTest.java | 22 +- .../list/views/HabitCardControllerTest.java | 16 +- 19 files changed, 462 insertions(+), 507 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/models/Reminder.java diff --git a/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java index db1b1ec10..ab3cfd775 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java @@ -19,69 +19,35 @@ package org.isoron.uhabits.espresso; -import android.app.Activity; -import android.app.Instrumentation; -import android.content.Context; -import android.content.Intent; -import android.support.test.InstrumentationRegistry; -import android.support.test.espresso.NoMatchingViewException; -import android.support.test.espresso.intent.rule.IntentsTestRule; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.LargeTest; +import android.app.*; +import android.content.*; +import android.support.test.*; +import android.support.test.espresso.*; +import android.support.test.espresso.intent.rule.*; +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; import org.hamcrest.*; +import org.isoron.uhabits.*; import org.isoron.uhabits.R; -import org.isoron.uhabits.models.sqlite.records.HabitRecord; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.MainActivity; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.LinkedList; -import java.util.List; -import java.util.Random; - -import static android.support.test.espresso.Espresso.onData; -import static android.support.test.espresso.Espresso.onView; +import org.isoron.uhabits.models.sqlite.records.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; +import org.junit.runner.*; + +import java.util.*; + +import static android.support.test.espresso.Espresso.*; import static android.support.test.espresso.Espresso.pressBack; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.action.ViewActions.longClick; -import static android.support.test.espresso.action.ViewActions.scrollTo; -import static android.support.test.espresso.action.ViewActions.swipeLeft; -import static android.support.test.espresso.action.ViewActions.swipeRight; -import static android.support.test.espresso.action.ViewActions.swipeUp; -import static android.support.test.espresso.assertion.ViewAssertions.matches; -import static android.support.test.espresso.intent.Intents.intended; -import static android.support.test.espresso.intent.Intents.intending; -import static android.support.test.espresso.intent.matcher.IntentMatchers.hasAction; -import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; -import static android.support.test.espresso.matcher.ViewMatchers.isRoot; -import static android.support.test.espresso.matcher.ViewMatchers.withClassName; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; -import static org.isoron.uhabits.espresso.HabitViewActions.clickAtRandomLocations; -import static org.isoron.uhabits.espresso.HabitViewActions.toggleAllCheckmarks; -import static org.isoron.uhabits.espresso.MainActivityActions.addHabit; -import static org.isoron.uhabits.espresso.MainActivityActions.assertHabitExists; -import static org.isoron.uhabits.espresso.MainActivityActions.assertHabitsDontExist; -import static org.isoron.uhabits.espresso.MainActivityActions.assertHabitsExist; -import static org.isoron.uhabits.espresso.MainActivityActions.clickMenuItem; -import static org.isoron.uhabits.espresso.MainActivityActions.clickSettingsItem; -import static org.isoron.uhabits.espresso.MainActivityActions.deleteHabit; -import static org.isoron.uhabits.espresso.MainActivityActions.deleteHabits; -import static org.isoron.uhabits.espresso.MainActivityActions.selectHabit; -import static org.isoron.uhabits.espresso.MainActivityActions.selectHabits; -import static org.isoron.uhabits.espresso.MainActivityActions.typeHabitData; -import static org.isoron.uhabits.espresso.ShowHabitActivityActions.openHistoryEditor; +import static android.support.test.espresso.action.ViewActions.*; +import static android.support.test.espresso.assertion.ViewAssertions.*; +import static android.support.test.espresso.intent.Intents.*; +import static android.support.test.espresso.intent.matcher.IntentMatchers.*; +import static android.support.test.espresso.matcher.ViewMatchers.*; +import static org.hamcrest.Matchers.*; +import static org.isoron.uhabits.espresso.HabitViewActions.*; +import static org.isoron.uhabits.espresso.MainActivityActions.*; +import static org.isoron.uhabits.espresso.ShowHabitActivityActions.*; @RunWith(AndroidJUnit4.class) @LargeTest @@ -90,45 +56,38 @@ public class MainTest private SystemHelper sys; @Rule - public IntentsTestRule activityRule = new IntentsTestRule<>( - MainActivity.class); - - private Context targetContext; + public IntentsTestRule activityRule = + new IntentsTestRule<>(MainActivity.class); @Before public void setup() { - Context context = InstrumentationRegistry.getInstrumentation().getContext(); + Context context = + InstrumentationRegistry.getInstrumentation().getContext(); sys = new SystemHelper(context); sys.disableAllAnimations(); sys.acquireWakeLock(); sys.unlockScreen(); - targetContext = InstrumentationRegistry.getTargetContext(); - - Instrumentation.ActivityResult okResult = new Instrumentation.ActivityResult( - Activity.RESULT_OK, new Intent()); + Instrumentation.ActivityResult okResult = + new Instrumentation.ActivityResult(Activity.RESULT_OK, + new Intent()); intending(hasAction(equalTo(Intent.ACTION_SEND))).respondWith(okResult); - intending(hasAction(equalTo(Intent.ACTION_SENDTO))).respondWith(okResult); + intending(hasAction(equalTo(Intent.ACTION_SENDTO))).respondWith( + okResult); intending(hasAction(equalTo(Intent.ACTION_VIEW))).respondWith(okResult); skipTutorial(); } - @After - public void tearDown() - { - sys.releaseWakeLock(); - } - public void skipTutorial() { try { for (int i = 0; i < 10; i++) onView(allOf(withClassName(endsWith("AppCompatImageButton")), - isDisplayed())).perform(click()); + isDisplayed())).perform(click()); } catch (NoMatchingViewException e) { @@ -136,44 +95,57 @@ public class MainTest } } + @After + public void tearDown() + { + sys.releaseWakeLock(); + } + /** - * User opens the app, creates some habits, selects them, archives them, select 'show archived' - * on the menu, selects the previously archived habits and then deletes them. + * User opens menu, clicks about, sees about screen. */ @Test - public void testArchiveHabits() + public void testAbout() { - List names = new LinkedList<>(); - - for(int i = 0; i < 3; i++) - names.add(addHabit()); + clickMenuItem(R.string.about); + onView(isRoot()).perform(swipeUp()); + } - selectHabits(names); + /** + * User creates a habit, toggles a bunch of checkmarks, clicks the habit to + * open the statistics screen, scrolls down to some views, then scrolls the + * views backwards and forwards in time. + */ + @Test + public void testAddHabitAndViewStats() throws InterruptedException + { + String name = addHabit(true); - clickMenuItem(R.string.archive); - assertHabitsDontExist(names); + onData(Matchers.allOf(is(instanceOf(HabitRecord.class)), + HabitMatchers.withName(name))) + .onChildView(withId(R.id.checkmarkPanel)) + .perform(toggleAllCheckmarks()); - clickMenuItem(R.string.show_archived); + Thread.sleep(1200); - assertHabitsExist(names); - selectHabits(names); - clickMenuItem(R.string.unarchive); + onData(Matchers.allOf(is(instanceOf(HabitRecord.class)), + HabitMatchers.withName(name))) + .onChildView(withId(R.id.label)) + .perform(click()); - clickMenuItem(R.string.show_archived); + onView(withId(R.id.scoreView)).perform(scrollTo(), swipeRight()); - assertHabitsExist(names); - deleteHabits(names); + onView(withId(R.id.punchcardView)).perform(scrollTo(), swipeRight()); } /** - * User opens the app, clicks the add button, types some bogus information, tries to save, - * dialog displays an error. + * User opens the app, clicks the add button, types some bogus information, + * tries to save, dialog displays an error. */ @Test public void testAddInvalidHabit() { - onView(withId(R.id.action_add)) - .perform(click()); + onView(withId(R.id.action_add)).perform(click()); typeHabitData("", "", "15", "7"); @@ -182,55 +154,56 @@ public class MainTest } /** - * User creates a habit, toggles a bunch of checkmarks, clicks the habit to open the statistics - * screen, scrolls down to some views, then scrolls the views backwards and forwards in time. + * User opens the app, creates some habits, selects them, archives them, + * select 'show archived' on the menu, selects the previously archived + * habits and then deletes them. */ @Test - public void testAddHabitAndViewStats() throws InterruptedException + public void testArchiveHabits() { - String name = addHabit(true); + List names = new LinkedList<>(); - onData( - Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) - .onChildView(withId(R.id.checkmarkPanel)) - .perform(toggleAllCheckmarks()); + for (int i = 0; i < 3; i++) + names.add(addHabit()); - Thread.sleep(1200); + selectHabits(names); - onData( - Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) - .onChildView(withId(R.id.label)) - .perform(click()); + clickMenuItem(R.string.archive); + assertHabitsDontExist(names); + + clickMenuItem(R.string.show_archived); - onView(withId(R.id.scoreView)) - .perform(scrollTo(), swipeRight()); + assertHabitsExist(names); + selectHabits(names); + clickMenuItem(R.string.unarchive); - onView(withId(R.id.punchcardView)) - .perform(scrollTo(), swipeRight()); + clickMenuItem(R.string.show_archived); + + assertHabitsExist(names); + deleteHabits(names); } /** - * User creates a habit, selects the habit, clicks edit button, changes some information about - * the habit, click save button, sees changes on the main window, selects habit again, - * changes color, then deletes the habit. + * User creates a habit, selects the habit, clicks edit button, changes some + * information about the habit, click save button, sees changes on the main + * window, selects habit again, changes color, then deletes the habit. */ @Test public void testEditHabit() { String name = addHabit(); - onData( - Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) - .onChildView(withId(R.id.label)) - .perform(longClick()); + onData(Matchers.allOf(is(instanceOf(HabitRecord.class)), + HabitMatchers.withName(name))) + .onChildView(withId(R.id.label)) + .perform(longClick()); clickMenuItem(R.string.edit); String modifiedName = "Modified " + new Random().nextInt(10000); typeHabitData(modifiedName, "", "1", "1"); - onView(withId(R.id.buttonSave)) - .perform(click()); + onView(withId(R.id.buttonSave)).perform(click()); assertHabitExists(modifiedName); @@ -242,60 +215,45 @@ public class MainTest } /** - * User creates a habit, opens statistics page, clicks button to edit history, adds some - * checkmarks, closes dialog, sees the modified history calendar. + * User creates a habit, opens statistics page, clicks button to edit + * history, adds some checkmarks, closes dialog, sees the modified history + * calendar. */ @Test public void testEditHistory() { String name = addHabit(); - onData( - Matchers.allOf(is(instanceOf(HabitRecord.class)), HabitMatchers.withName(name))) - .onChildView(withId(R.id.label)) - .perform(click()); + onData(Matchers.allOf(is(instanceOf(HabitRecord.class)), + HabitMatchers.withName(name))) + .onChildView(withId(R.id.label)) + .perform(click()); openHistoryEditor(); - onView(withClassName(endsWith("HabitHistoryView"))) - .perform(clickAtRandomLocations(20)); + onView(withClassName(endsWith("HabitHistoryView"))).perform( + clickAtRandomLocations(20)); pressBack(); - onView(withId(R.id.historyView)) - .perform(scrollTo(), swipeRight(), swipeLeft()); + onView(withId(R.id.historyView)).perform(scrollTo(), swipeRight(), + swipeLeft()); } /** - * User opens menu, clicks settings, sees settings screen. + * User creates a habit, opens settings, clicks export as CSV, is asked what + * activity should handle the file. */ @Test - public void testSettings() + public void testExportCSV() { + addHabit(); clickMenuItem(R.string.settings); + clickSettingsItem("Export as CSV"); + intended(hasAction(Intent.ACTION_SEND)); } /** - * User opens menu, clicks about, sees about screen. - */ - @Test - public void testAbout() - { - clickMenuItem(R.string.about); - onView(isRoot()).perform(swipeUp()); - } - - /** - * User opens menu, clicks Help, sees website. - */ - @Test - public void testHelp() - { - clickMenuItem(R.string.help); - intended(hasAction(Intent.ACTION_VIEW)); - } - - /** - * User creates a habit, exports full backup, deletes the habit, restores backup, sees that the - * previously created habit has appeared back. + * User creates a habit, exports full backup, deletes the habit, restores + * backup, sees that the previously created habit has appeared back. */ @Test public void testExportImportDB() @@ -304,7 +262,8 @@ public class MainTest clickMenuItem(R.string.settings); - String date = DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); + String date = + DateUtils.getBackupDateFormat().format(DateUtils.getLocalTime()); date = date.substring(0, date.length() - 2); clickSettingsItem("Export full backup"); @@ -315,36 +274,44 @@ public class MainTest clickMenuItem(R.string.settings); clickSettingsItem("Import data"); - onData(allOf(is(instanceOf(String.class)), startsWith("Backups"))) - .perform(click()); + onData( + allOf(is(instanceOf(String.class)), startsWith("Backups"))).perform( + click()); - onData(allOf(is(instanceOf(String.class)), containsString(date))) - .perform(click()); + onData( + allOf(is(instanceOf(String.class)), containsString(date))).perform( + click()); selectHabit(name); } /** - * User creates a habit, opens settings, clicks export as CSV, is asked what activity should - * handle the file. + * User opens the settings and generates a bug report. */ @Test - public void testExportCSV() + public void testGenerateBugReport() { - addHabit(); clickMenuItem(R.string.settings); - clickSettingsItem("Export as CSV"); + clickSettingsItem("Generate bug report"); intended(hasAction(Intent.ACTION_SEND)); } /** - * User opens the settings and generates a bug report. + * User opens menu, clicks Help, sees website. */ @Test - public void testGenerateBugReport() + public void testHelp() + { + clickMenuItem(R.string.help); + intended(hasAction(Intent.ACTION_VIEW)); + } + + /** + * User opens menu, clicks settings, sees settings screen. + */ + @Test + public void testSettings() { clickMenuItem(R.string.settings); - clickSettingsItem("Generate bug report"); - intended(hasAction(Intent.ACTION_SEND)); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java index 431ad7ab0..fbce70552 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java @@ -19,31 +19,22 @@ package org.isoron.uhabits.io; -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; - -import org.isoron.uhabits.BaseAndroidTest; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.FileUtils; -import org.isoron.uhabits.utils.DateUtils; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.GregorianCalendar; -import java.util.List; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import android.content.*; +import android.support.test.*; +import android.support.test.runner.*; +import android.test.suitebuilder.annotation.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; +import org.junit.runner.*; + +import java.io.*; +import java.util.*; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; @RunWith(AndroidJUnit4.class) @SmallTest @@ -128,10 +119,13 @@ public class ImportTest extends BaseAndroidTest assertThat(habit.getName(), equalTo("brush teeth")); assertThat(habit.getFreqNum(), equalTo(3)); assertThat(habit.getFreqDen(), equalTo(7)); - assertThat(habit.getReminderHour(), equalTo(8)); - assertThat(habit.getReminderMin(), equalTo(0)); + assertThat(habit.hasReminder(), equalTo(true)); + + Reminder reminder = habit.getReminder(); + assertThat(reminder.getHour(), equalTo(8)); + assertThat(reminder.getMinute(), equalTo(0)); boolean[] reminderDays = {false, true, true, true, true, true, false}; - assertThat(habit.getReminderDays(), equalTo(DateUtils.packWeekdayList(reminderDays))); + assertThat(reminder.getDays(), equalTo(DateUtils.packWeekdayList(reminderDays))); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java index 4ccb87016..d66e3bc12 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteCheckmarkListTest.java @@ -67,9 +67,9 @@ public class SQLiteCheckmarkListTest extends BaseAndroidTest checkmarks.invalidateNewerThan(0); List list = new LinkedList<>(); - list.add(new Checkmark(habit, 0, 0)); - list.add(new Checkmark(habit, 1, 1)); - list.add(new Checkmark(habit, 2, 2)); + list.add(new Checkmark(0, 0)); + list.add(new Checkmark(1, 1)); + list.add(new Checkmark(2, 2)); checkmarks.add(list); diff --git a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java index 561290636..fdb10c42a 100644 --- a/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java +++ b/app/src/main/java/org/isoron/uhabits/HabitBroadcastReceiver.java @@ -38,9 +38,7 @@ import android.support.v4.content.LocalBroadcastManager; import org.isoron.uhabits.commands.CommandRunner; import org.isoron.uhabits.commands.ToggleRepetitionCommand; -import org.isoron.uhabits.models.Checkmark; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.models.*; import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.ui.habits.show.ShowHabitActivity; import org.isoron.uhabits.utils.DateUtils; @@ -192,11 +190,14 @@ public class HabitBroadcastReceiver extends BroadcastReceiver private boolean checkWeekday(Intent intent, Habit habit) { + if(!habit.hasReminder()) return false; + Reminder reminder = habit.getReminder(); + Long timestamp = intent.getLongExtra("timestamp", DateUtils.getStartOfToday()); boolean reminderDays[] = - DateUtils.unpackWeekdayList(habit.getReminderDays()); + DateUtils.unpackWeekdayList(reminder.getDays()); int weekday = DateUtils.getWeekday(timestamp); return reminderDays[weekday]; diff --git a/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java index 82d36282d..f91895df9 100644 --- a/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java @@ -19,17 +19,16 @@ package org.isoron.uhabits.io; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.NonNull; +import android.database.*; +import android.database.sqlite.*; +import android.support.annotation.*; -import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.DatabaseUtils; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.utils.*; -import java.io.File; -import java.io.IOException; -import java.util.GregorianCalendar; +import java.io.*; +import java.util.*; /** * Class that imports database files exported by Rewire. @@ -39,13 +38,14 @@ public class RewireDBImporter extends AbstractImporter @Override public boolean canHandle(@NonNull File file) throws IOException { - if(!isSQLite3File(file)) return false; + if (!isSQLite3File(file)) return false; SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null, - SQLiteDatabase.OPEN_READONLY); + SQLiteDatabase.OPEN_READONLY); - Cursor c = db.rawQuery("select count(*) from SQLITE_MASTER where name=? or name=?", - new String[]{"CHECKINS", "UNIT"}); + Cursor c = db.rawQuery( + "select count(*) from SQLITE_MASTER where name=? or name=?", + new String[]{ "CHECKINS", "UNIT" }); boolean result = (c.moveToFirst() && c.getInt(0) == 2); @@ -57,7 +57,8 @@ public class RewireDBImporter extends AbstractImporter @Override public void importHabitsFromFile(@NonNull File file) throws IOException { - final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null, + final SQLiteDatabase db = + SQLiteDatabase.openDatabase(file.getPath(), null, SQLiteDatabase.OPEN_READONLY); DatabaseUtils.executeAsTransaction(new DatabaseUtils.Callback() @@ -72,14 +73,48 @@ public class RewireDBImporter extends AbstractImporter db.close(); } + private void createCheckmarks(@NonNull SQLiteDatabase db, + @NonNull Habit habit, + int rewireHabitId) + { + Cursor c = null; + + try + { + String[] params = { Integer.toString(rewireHabitId) }; + c = db.rawQuery( + "select distinct date from checkins where habit_id=? and type=2", + params); + if (!c.moveToFirst()) return; + + do + { + String date = c.getString(0); + int year = Integer.parseInt(date.substring(0, 4)); + int month = Integer.parseInt(date.substring(4, 6)); + int day = Integer.parseInt(date.substring(6, 8)); + + GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); + cal.set(year, month - 1, day); + + habit.getRepetitions().toggleTimestamp(cal.getTimeInMillis()); + } while (c.moveToNext()); + } + finally + { + if (c != null) c.close(); + } + } + private void createHabits(SQLiteDatabase db) { Cursor c = null; try { - c = db.rawQuery("select _id, name, description, schedule, active_days, " + - "repeating_count, days, period from habits", new String[0]); + c = db.rawQuery( + "select _id, name, description, schedule, active_days, " + + "repeating_count, days, period from habits", new String[0]); if (!c.moveToFirst()) return; do @@ -122,8 +157,7 @@ public class RewireDBImporter extends AbstractImporter createReminder(db, habit, id); createCheckmarks(db, habit, id); - } - while (c.moveToNext()); + } while (c.moveToNext()); } finally { @@ -131,14 +165,18 @@ public class RewireDBImporter extends AbstractImporter } } - private void createReminder(SQLiteDatabase db, Habit habit, int rewireHabitId) + private void createReminder(SQLiteDatabase db, + Habit habit, + int rewireHabitId) { String[] params = { Integer.toString(rewireHabitId) }; Cursor c = null; try { - c = db.rawQuery("select time, active_days from reminders where habit_id=? limit 1", params); + c = db.rawQuery( + "select time, active_days from reminders where habit_id=? limit 1", + params); if (!c.moveToFirst()) return; int rewireReminder = Integer.parseInt(c.getString(0)); @@ -147,47 +185,19 @@ public class RewireDBImporter extends AbstractImporter boolean reminderDays[] = new boolean[7]; String activeDays[] = c.getString(1).split(","); - for(String d : activeDays) + for (String d : activeDays) { int idx = (Integer.parseInt(d) + 1) % 7; reminderDays[idx] = true; } - habit.setReminderDays(DateUtils.packWeekdayList(reminderDays)); - habit.setReminderHour(rewireReminder / 60); - habit.setReminderMin(rewireReminder % 60); - habitList.update(habit); - } - finally - { - if(c != null) c.close(); - } - } - - private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull - Habit habit, int rewireHabitId) - { - Cursor c = null; - - try - { - String[] params = { Integer.toString(rewireHabitId) }; - c = db.rawQuery("select distinct date from checkins where habit_id=? and type=2", params); - if (!c.moveToFirst()) return; + int hour = rewireReminder / 60; + int minute = rewireReminder % 60; + Integer days = DateUtils.packWeekdayList(reminderDays); - do - { - String date = c.getString(0); - int year = Integer.parseInt(date.substring(0, 4)); - int month = Integer.parseInt(date.substring(4, 6)); - int day = Integer.parseInt(date.substring(6, 8)); - - GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); - cal.set(year, month - 1, day); - - habit.getRepetitions().toggleTimestamp(cal.getTimeInMillis()); - } - while (c.moveToNext()); + Reminder reminder = new Reminder(hour, minute, days); + habit.setReminder(reminder); + habitList.update(habit); } finally { diff --git a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java index 29348b0b7..ccc61c194 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java +++ b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java @@ -49,15 +49,12 @@ public class Checkmark */ public static final int UNCHECKED = 0; - private final Habit habit; - private final long timestamp; private final int value; - public Checkmark(Habit habit, long timestamp, int value) + public Checkmark(long timestamp, int value) { - this.habit = habit; this.timestamp = timestamp; this.value = value; } @@ -67,11 +64,6 @@ public class Checkmark return Long.signum(this.getTimestamp() - other.getTimestamp()); } - public Habit getHabit() - { - return habit; - } - public long getTimestamp() { return timestamp; 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 89b05d63a..3f6f26ba2 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -221,7 +221,7 @@ public abstract class CheckmarkList { int value = checks[i]; long timestamp = to - i * day; - checkmarks.add(new Checkmark(habit, timestamp, value)); + checkmarks.add(new Checkmark(timestamp, value)); } add(checkmarks); diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 00c11d16d..381fccaa4 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -24,7 +24,6 @@ import android.support.annotation.*; import org.apache.commons.lang3.builder.*; import org.isoron.uhabits.*; -import org.isoron.uhabits.utils.*; import java.util.*; @@ -56,15 +55,6 @@ public class Habit @NonNull private Integer color; - @Nullable - private Integer reminderHour; - - @Nullable - private Integer reminderMin; - - @NonNull - private Integer reminderDays; - @NonNull private Integer highlight; @@ -83,6 +73,9 @@ public class Habit @NonNull private CheckmarkList checkmarks; + @Nullable + private Reminder reminder; + private ModelObservable observable = new ModelObservable(); @Inject @@ -97,8 +90,6 @@ public class Habit { HabitsApplication.getComponent().inject(this); - reminderDays = DateUtils.ALL_WEEK_DAYS; - copyFrom(model); checkmarks = factory.buildCheckmarkList(this); @@ -122,7 +113,6 @@ public class Habit this.archived = 0; this.freqDen = 7; this.freqNum = 3; - this.reminderDays = DateUtils.ALL_WEEK_DAYS; checkmarks = factory.buildCheckmarkList(this); streaks = factory.buildStreakList(this); @@ -136,9 +126,7 @@ public class Habit */ public void clearReminder() { - reminderHour = null; - reminderMin = null; - reminderDays = DateUtils.ALL_WEEK_DAYS; + reminder = null; observable.notifyListeners(); } @@ -154,9 +142,7 @@ public class Habit this.freqNum = model.getFreqNum(); this.freqDen = model.getFreqDen(); this.color = model.getColor(); - this.reminderHour = model.getReminderHour(); - this.reminderMin = model.getReminderMin(); - this.reminderDays = model.getReminderDays(); + this.reminder = model.reminder; this.highlight = model.getHighlight(); this.archived = model.getArchived(); observable.notifyListeners(); @@ -193,6 +179,13 @@ public class Habit return color; } + @NonNull + public Reminder getReminder() + { + if(reminder == null) throw new IllegalStateException(); + return reminder; + } + public void setColor(Integer color) { this.color = color; @@ -283,54 +276,6 @@ public class Habit return observable; } - /** - * Days of the week the reminder should be shown. This field can be - * converted to a list of booleans using the method DateHelper.unpackWeekdayList - * and converted back to an integer by using the method - * DateHelper.packWeekdayList. If the habit has no reminders, this value - * should be ignored. - */ - @NonNull - public Integer getReminderDays() - { - return reminderDays; - } - - public void setReminderDays(@NonNull Integer reminderDays) - { - this.reminderDays = reminderDays; - } - - /** - * Hour of the day the reminder should be shown. If there is no reminder, - * this equals to null. - */ - @Nullable - public Integer getReminderHour() - { - return reminderHour; - } - - public void setReminderHour(@Nullable Integer reminderHour) - { - this.reminderHour = reminderHour; - } - - /** - * Minute the reminder should be shown. If there is no reminder, this equals - * to null. - */ - @Nullable - public Integer getReminderMin() - { - return reminderMin; - } - - public void setReminderMin(@Nullable Integer reminderMin) - { - this.reminderMin = reminderMin; - } - /** * List of repetitions belonging to this habit. */ @@ -376,7 +321,7 @@ public class Habit */ public boolean hasReminder() { - return (reminderHour != null && reminderMin != null); + return reminder != null; } /** @@ -394,6 +339,11 @@ public class Habit this.archived = archived; } + public void setReminder(@Nullable Reminder reminder) + { + this.reminder = reminder; + } + @Override public String toString() { @@ -404,9 +354,6 @@ public class Habit .append("freqNum", freqNum) .append("freqDen", freqDen) .append("color", color) - .append("reminderHour", reminderHour) - .append("reminderMin", reminderMin) - .append("reminderDays", reminderDays) .append("highlight", highlight) .append("archived", archived) .toString(); diff --git a/app/src/main/java/org/isoron/uhabits/models/Reminder.java b/app/src/main/java/org/isoron/uhabits/models/Reminder.java new file mode 100644 index 000000000..e92bef33a --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/Reminder.java @@ -0,0 +1,58 @@ +/* + * 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; + +public class Reminder +{ + private final int hour; + + private final int minute; + + private final int days; + + public Reminder(int hour, int minute, int days) + { + this.hour = hour; + this.minute = minute; + this.days = days; + } + + /** + * Returns the days of the week the reminder should be shown. + *

+ * This field can be converted to a list of booleans using the method + * DateHelper.unpackWeekdayList and converted back to an integer by using + * the method DateHelper.packWeekdayList. + */ + public int getDays() + { + return days; + } + + public int getHour() + { + return hour; + } + + public int getMinute() + { + return minute; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java index 3e5f060b9..718aef00b 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/CheckmarkRecord.java @@ -65,6 +65,6 @@ public class CheckmarkRecord extends Model implements SQLiteRecord { SQLiteHabitList habitList = SQLiteHabitList.getInstance(); Habit h = habitList.getById(habit.getId()); - return new Checkmark(h, timestamp, value); + return new Checkmark(timestamp, value); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java index 3d6c7553f..738232756 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java @@ -39,9 +39,6 @@ import java.lang.reflect.*; @Table(name = "Habits") public class HabitRecord extends Model implements SQLiteRecord { - public static final String HABIT_URI_FORMAT = - "content://org.isoron.uhabits/habit/%d"; - public static String SELECT = "select id, color, description, freq_den, freq_num, " + "name, position, reminder_hour, reminder_min, " + @@ -148,9 +145,13 @@ public class HabitRecord extends Model implements SQLiteRecord this.freqNum = model.getFreqNum(); this.freqDen = model.getFreqDen(); this.color = model.getColor(); - this.reminderHour = model.getReminderHour(); - this.reminderMin = model.getReminderMin(); - this.reminderDays = model.getReminderDays(); + if(model.hasReminder()) + { + Reminder reminder = model.getReminder(); + this.reminderHour = reminder.getHour(); + this.reminderMin = reminder.getMinute(); + this.reminderDays = reminder.getDays(); + } this.highlight = model.getHighlight(); this.archived = model.getArchived(); } @@ -171,7 +172,7 @@ public class HabitRecord extends Model implements SQLiteRecord archived = c.getInt(10); reminderDays = c.getInt(11); } - + public void copyTo(Habit habit) { habit.setName(this.name); @@ -179,12 +180,15 @@ public class HabitRecord extends Model implements SQLiteRecord habit.setFreqNum(this.freqNum); habit.setFreqDen(this.freqDen); habit.setColor(this.color); - habit.setReminderHour(this.reminderHour); - habit.setReminderMin(this.reminderMin); - habit.setReminderDays(this.reminderDays); habit.setHighlight(this.highlight); habit.setArchived(this.archived); habit.setId(this.getId()); + + if (reminderHour != null && reminderMin != null) + { + habit.setReminder( + new Reminder(reminderHour, reminderMin, reminderDays)); + } } /** diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java index 07d50342b..d0ee67b85 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java @@ -19,35 +19,26 @@ package org.isoron.uhabits.ui.habits.edit; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatDialogFragment; -import android.text.format.DateFormat; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.colorpicker.ColorPickerDialog; -import com.android.colorpicker.ColorPickerSwatch; -import com.android.datetimepicker.time.RadialPickerLayout; -import com.android.datetimepicker.time.TimePickerDialog; - -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.CommandRunner; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.utils.ColorUtils; +import android.os.*; +import android.support.annotation.*; +import android.support.v7.app.*; +import android.text.format.*; +import android.view.*; + +import com.android.colorpicker.*; +import com.android.datetimepicker.time.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.Preferences; -import java.util.Arrays; +import java.util.*; -import javax.inject.Inject; +import javax.inject.*; -import butterknife.ButterKnife; -import butterknife.OnClick; -import butterknife.OnItemSelected; +import butterknife.*; public abstract class BaseDialogFragment extends AppCompatDialogFragment { @@ -90,8 +81,8 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment public void onFrequencySelected(int position) { if (position < 0 || position > 4) throw new IllegalArgumentException(); - int freqNums[] = {1, 1, 2, 5, 3}; - int freqDens[] = {1, 7, 7, 7, 7}; + int freqNums[] = { 1, 1, 2, 5, 3 }; + int freqDens[] = { 1, 7, 7, 7, 7 }; modifiedHabit.setFreqNum(freqNums[position]); modifiedHabit.setFreqDen(freqDens[position]); helper.populateFrequencyFields(modifiedHabit); @@ -105,9 +96,10 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment outState.putInt("color", modifiedHabit.getColor()); if (modifiedHabit.hasReminder()) { - outState.putInt("reminderMin", modifiedHabit.getReminderMin()); - outState.putInt("reminderHour", modifiedHabit.getReminderHour()); - outState.putInt("reminderDays", modifiedHabit.getReminderDays()); + Reminder reminder = modifiedHabit.getReminder(); + outState.putInt("reminderMin", reminder.getMinute()); + outState.putInt("reminderHour", reminder.getHour()); + outState.putInt("reminderDays", reminder.getDays()); } } @@ -115,6 +107,28 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment protected abstract void initializeHabits(); + protected void restoreSavedInstance(@Nullable Bundle bundle) + { + if (bundle == null) return; + modifiedHabit.setColor( + bundle.getInt("color", modifiedHabit.getColor())); + + + modifiedHabit.setReminder(null); + + int hour = (bundle.getInt("reminderHour", -1)); + int minute = (bundle.getInt("reminderMin", -1)); + int days = (bundle.getInt("reminderDays", -1)); + + if (hour >= 0 && minute >= 0) + { + Reminder reminder = new Reminder(hour, minute, days); + modifiedHabit.setReminder(reminder); + } + } + + protected abstract void saveHabit(); + @OnClick(R.id.buttonDiscard) void onButtonDiscardClick() { @@ -130,8 +144,9 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment if (modifiedHabit.hasReminder()) { - defaultHour = modifiedHabit.getReminderHour(); - defaultMin = modifiedHabit.getReminderMin(); + Reminder reminder = modifiedHabit.getReminder(); + defaultHour = reminder.getHour(); + defaultMin = reminder.getMinute(); } showTimePicker(defaultHour, defaultMin); @@ -151,26 +166,15 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment void onWeekdayClick() { if (!modifiedHabit.hasReminder()) return; + Reminder reminder = modifiedHabit.getReminder(); + WeekdayPickerDialog dialog = new WeekdayPickerDialog(); dialog.setListener(new OnWeekdaysPickedListener()); dialog.setSelectedDays( - DateUtils.unpackWeekdayList(modifiedHabit.getReminderDays())); + DateUtils.unpackWeekdayList(reminder.getDays())); dialog.show(getFragmentManager(), "weekdayPicker"); } - protected void restoreSavedInstance(@Nullable Bundle bundle) - { - if (bundle == null) return; - modifiedHabit.setColor( - bundle.getInt("color", modifiedHabit.getColor())); - modifiedHabit.setReminderMin(bundle.getInt("reminderMin", -1)); - modifiedHabit.setReminderHour(bundle.getInt("reminderHour", -1)); - modifiedHabit.setReminderDays(bundle.getInt("reminderDays", -1)); - if (modifiedHabit.getReminderMin() < 0) modifiedHabit.clearReminder(); - } - - protected abstract void saveHabit(); - @OnClick(R.id.buttonPickColor) void showColorPicker() { @@ -222,9 +226,9 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment @Override public void onTimeSet(RadialPickerLayout view, int hour, int minute) { - modifiedHabit.setReminderHour(hour); - modifiedHabit.setReminderMin(minute); - modifiedHabit.setReminderDays(DateUtils.ALL_WEEK_DAYS); + Reminder reminder = + new Reminder(hour, minute, DateUtils.ALL_WEEK_DAYS); + modifiedHabit.setReminder(reminder); helper.populateReminderFields(modifiedHabit); } } @@ -237,8 +241,10 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment { if (isSelectionEmpty(selectedDays)) Arrays.fill(selectedDays, true); - modifiedHabit.setReminderDays( - DateUtils.packWeekdayList(selectedDays)); + Reminder oldReminder = modifiedHabit.getReminder(); + modifiedHabit.setReminder( + new Reminder(oldReminder.getHour(), oldReminder.getMinute(), + DateUtils.packWeekdayList(selectedDays))); helper.populateReminderFields(modifiedHabit); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java index 993ba6839..93790f403 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java @@ -19,20 +19,16 @@ package org.isoron.uhabits.ui.habits.edit; -import android.annotation.SuppressLint; -import android.support.v4.app.DialogFragment; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Spinner; -import android.widget.TextView; +import android.annotation.*; +import android.support.v4.app.*; +import android.view.*; +import android.widget.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.ColorUtils; -import org.isoron.uhabits.utils.DateUtils; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; -import butterknife.BindView; -import butterknife.ButterKnife; +import butterknife.*; public class BaseDialogHelper { @@ -71,6 +67,17 @@ public class BaseDialogHelper ButterKnife.bind(this, view); } + protected void populateForm(final Habit habit) + { + if (habit.getName() != null) tvName.setText(habit.getName()); + if (habit.getDescription() != null) + tvDescription.setText(habit.getDescription()); + + populateColor(habit.getColor()); + populateFrequencyFields(habit); + populateReminderFields(habit); + } + void parseFormIntoHabit(Habit habit) { habit.setName(tvName.getText().toString().trim()); @@ -87,23 +94,13 @@ public class BaseDialogHelper ColorUtils.getColor(frag.getContext(), paletteColor)); } - protected void populateForm(final Habit habit) - { - if (habit.getName() != null) tvName.setText(habit.getName()); - if (habit.getDescription() != null) tvDescription.setText( - habit.getDescription()); - - populateColor(habit.getColor()); - populateFrequencyFields(habit); - populateReminderFields(habit); - } - @SuppressLint("SetTextI18n") void populateFrequencyFields(Habit habit) { int quickSelectPosition = -1; - if (habit.getFreqNum().equals(habit.getFreqDen())) quickSelectPosition = 0; + if (habit.getFreqNum().equals(habit.getFreqDen())) + quickSelectPosition = 0; else if (habit.getFreqNum() == 1 && habit.getFreqDen() == 7) quickSelectPosition = 1; @@ -133,14 +130,16 @@ public class BaseDialogHelper return; } + Reminder reminder = habit.getReminder(); + String time = - DateUtils.formatTime(frag.getContext(), habit.getReminderHour(), - habit.getReminderMin()); + DateUtils.formatTime(frag.getContext(), reminder.getHour(), + reminder.getMinute()); tvReminderTime.setText(time); llReminderDays.setVisibility(View.VISIBLE); - boolean weekdays[] = DateUtils.unpackWeekdayList( - habit.getReminderDays()); + boolean weekdays[] = + DateUtils.unpackWeekdayList(reminder.getDays()); tvReminderDays.setText( DateUtils.formatWeekdayList(frag.getContext(), weekdays)); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java index d0a340007..d6a53096a 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -101,12 +101,19 @@ public class ShowHabitHelper TextView reminderLabel = (TextView) view.findViewById(R.id.reminderLabel); - if (fragment.habit.hasReminder()) reminderLabel.setText( - DateUtils.formatTime(fragment.getActivity(), - fragment.habit.getReminderHour(), - fragment.habit.getReminderMin())); - else reminderLabel.setText( - fragment.getResources().getString(R.string.reminder_off)); + + if (fragment.habit.hasReminder()) + { + Reminder reminder = fragment.habit.getReminder(); + reminderLabel.setText( + DateUtils.formatTime(fragment.getActivity(), reminder.getHour(), + reminder.getMinute())); + } + else + { + reminderLabel.setText( + fragment.getResources().getString(R.string.reminder_off)); + } TextView frequencyLabel = (TextView) view.findViewById(R.id.frequencyLabel); diff --git a/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java b/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java index a770c6c7c..52ab9ed29 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/ReminderUtils.java @@ -19,29 +19,22 @@ package org.isoron.uhabits.utils; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Build; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.support.annotation.Nullable; +import android.app.*; +import android.content.*; +import android.media.*; +import android.net.*; +import android.os.*; +import android.preference.*; +import android.provider.*; +import android.support.annotation.*; import android.support.v4.app.Fragment; -import android.util.Log; +import android.util.*; -import org.isoron.uhabits.HabitBroadcastReceiver; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; -import java.text.DateFormat; -import java.util.Calendar; -import java.util.Date; +import java.text.*; +import java.util.*; public abstract class ReminderUtils { @@ -50,15 +43,14 @@ public abstract class ReminderUtils @Nullable Long reminderTime) { if (!habit.hasReminder()) return; + Reminder reminder = habit.getReminder(); if (reminderTime == null) { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); - //noinspection ConstantConditions - calendar.set(Calendar.HOUR_OF_DAY, habit.getReminderHour()); - //noinspection ConstantConditions - calendar.set(Calendar.MINUTE, habit.getReminderMin()); + calendar.set(Calendar.HOUR_OF_DAY, reminder.getHour()); + calendar.set(Calendar.MINUTE, reminder.getMinute()); calendar.set(Calendar.SECOND, 0); reminderTime = calendar.getTimeInMillis(); diff --git a/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java index 1203370af..e286ca01a 100644 --- a/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java +++ b/app/src/test/java/org/isoron/uhabits/BaseUnitTest.java @@ -19,16 +19,11 @@ package org.isoron.uhabits; -import org.isoron.uhabits.models.HabitFixtures; -import org.isoron.uhabits.models.HabitList; -import org.isoron.uhabits.models.ModelFactory; -import org.isoron.uhabits.ui.habits.list.model.HabitCardListCache; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.Preferences; -import org.junit.After; -import org.junit.Before; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; -import javax.inject.Inject; +import javax.inject.*; public class BaseUnitTest { @@ -38,9 +33,6 @@ public class BaseUnitTest @Inject protected Preferences prefs; - @Inject - protected HabitCardListCache listCache; - @Inject protected ModelFactory modelFactory; diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java b/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java index 698b67322..7175819a6 100644 --- a/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java @@ -54,11 +54,7 @@ public class HabitListTest extends BaseUnitTest list.add(habit); if (i % 3 == 0) - { - habit.setReminderDays(DateUtils.ALL_WEEK_DAYS); - habit.setReminderHour(8); - habit.setReminderMin(30); - } + habit.setReminder(new Reminder(8, 30, DateUtils.ALL_WEEK_DAYS)); } habits.get(0).setArchived(1); @@ -130,14 +126,14 @@ public class HabitListTest extends BaseUnitTest public void test_reorder() { int operations[][] = { - {5, 2}, {3, 7}, {4, 4}, {3, 2} + { 5, 2 }, { 3, 7 }, { 4, 4 }, { 3, 2 } }; int expectedPosition[][] = { - {0, 1, 3, 4, 5, 2, 6, 7, 8, 9}, - {0, 1, 7, 3, 4, 2, 5, 6, 8, 9}, - {0, 1, 7, 3, 4, 2, 5, 6, 8, 9}, - {0, 1, 7, 2, 4, 3, 5, 6, 8, 9}, + { 0, 1, 3, 4, 5, 2, 6, 7, 8, 9 }, + { 0, 1, 7, 3, 4, 2, 5, 6, 8, 9 }, + { 0, 1, 7, 3, 4, 2, 5, 6, 8, 9 }, + { 0, 1, 7, 2, 4, 3, 5, 6, 8, 9 }, }; for (int i = 0; i < operations.length; i++) diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitTest.java b/app/src/test/java/org/isoron/uhabits/models/HabitTest.java index 443e97cf8..5bc1b1f26 100644 --- a/app/src/test/java/org/isoron/uhabits/models/HabitTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/HabitTest.java @@ -23,6 +23,7 @@ import org.isoron.uhabits.BaseUnitTest; import org.isoron.uhabits.utils.DateUtils; import org.junit.Test; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.core.IsNot.not; @@ -38,12 +39,9 @@ public class HabitTest extends BaseUnitTest assertThat(habit.getArchived(), is(0)); assertThat(habit.getHighlight(), is(0)); - assertThat(habit.getReminderHour(), is(nullValue())); - assertThat(habit.getReminderMin(), is(nullValue())); - - assertThat(habit.getReminderDays(), is(not(nullValue()))); -// assertThat(habit.getStreaks(), is(not(nullValue()))); -// assertThat(habit.getScores(), is(not(nullValue()))); + assertThat(habit.hasReminder(), is(false)); + assertThat(habit.getStreaks(), is(not(nullValue()))); + assertThat(habit.getScores(), is(not(nullValue()))); assertThat(habit.getRepetitions(), is(not(nullValue()))); assertThat(habit.getCheckmarks(), is(not(nullValue()))); } @@ -57,9 +55,7 @@ public class HabitTest extends BaseUnitTest model.setColor(0); model.setFreqNum(10); model.setFreqDen(20); - model.setReminderDays(1); - model.setReminderHour(8); - model.setReminderMin(30); + model.setReminder(new Reminder(8, 30, 1)); Habit habit = new Habit(); habit.copyFrom(model); @@ -68,9 +64,7 @@ public class HabitTest extends BaseUnitTest assertThat(habit.getColor(), is(model.getColor())); assertThat(habit.getFreqNum(), is(model.getFreqNum())); assertThat(habit.getFreqDen(), is(model.getFreqDen())); - assertThat(habit.getReminderDays(), is(model.getReminderDays())); - assertThat(habit.getReminderHour(), is(model.getReminderHour())); - assertThat(habit.getReminderMin(), is(model.getReminderMin())); + assertThat(habit.getReminder(), equalTo(model.getReminder())); } // @Test @@ -103,9 +97,7 @@ public class HabitTest extends BaseUnitTest Habit h = new Habit(); assertThat(h.hasReminder(), is(false)); - h.setReminderDays(DateUtils.ALL_WEEK_DAYS); - h.setReminderHour(8); - h.setReminderMin(30); + h.setReminder(new Reminder(8, 30, DateUtils.ALL_WEEK_DAYS)); assertThat(h.hasReminder(), is(true)); h.clearReminder(); diff --git a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java index 2f7839589..3accec05a 100644 --- a/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java +++ b/app/src/test/java/org/isoron/uhabits/ui/habits/list/views/HabitCardControllerTest.java @@ -19,15 +19,13 @@ package org.isoron.uhabits.ui.habits.list.views; -import org.isoron.uhabits.BaseUnitTest; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.habits.list.controllers.HabitCardController; -import org.isoron.uhabits.utils.DateUtils; -import org.junit.Before; -import org.junit.Test; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.list.controllers.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; public class HabitCardControllerTest extends BaseUnitTest { @@ -68,7 +66,7 @@ public class HabitCardControllerTest extends BaseUnitTest { long timestamp = DateUtils.getStartOfToday(); controller.onToggle(habit, timestamp); - verify(view).triggerRipple(0, 0); + verify(view).triggerRipple(timestamp); verify(listener).onToggle(habit, timestamp); } } \ No newline at end of file From ec4a381d7076935e594fd618dd0eca5d04dbc722 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 17 Jun 2016 06:01:59 -0400 Subject: [PATCH 032/184] Refactor Habit class --- .../org/isoron/uhabits/HabitFixtures.java | 12 +- .../org/isoron/uhabits/io/ImportTest.java | 121 ++++++++-------- .../models/sqlite/SQLiteHabitListTest.java | 2 +- .../sqlite/SQLiteRepetitionListTest.java | 4 +- .../models/sqlite/SQLiteScoreListTest.java | 6 +- .../commands/ArchiveHabitsCommand.java | 4 +- .../uhabits/commands/EditHabitCommand.java | 18 ++- .../commands/UnarchiveHabitsCommand.java | 4 +- .../uhabits/io/HabitBullCSVImporter.java | 5 +- .../isoron/uhabits/io/RewireDBImporter.java | 17 ++- .../isoron/uhabits/io/TickmateDBImporter.java | 5 +- .../org/isoron/uhabits/models/Checkmark.java | 2 +- .../isoron/uhabits/models/CheckmarkList.java | 8 +- .../org/isoron/uhabits/models/Frequency.java | 97 +++++++++++++ .../java/org/isoron/uhabits/models/Habit.java | 129 +++++------------- .../org/isoron/uhabits/models/HabitList.java | 6 +- .../uhabits/models/ModelObservable.java | 8 +- .../org/isoron/uhabits/models/Reminder.java | 2 +- .../org/isoron/uhabits/models/Repetition.java | 15 +- .../isoron/uhabits/models/RepetitionList.java | 3 +- .../java/org/isoron/uhabits/models/Score.java | 15 +- .../org/isoron/uhabits/models/ScoreList.java | 6 +- .../org/isoron/uhabits/models/Streak.java | 16 +-- .../org/isoron/uhabits/models/StreakList.java | 2 +- .../models/sqlite/SQLiteRepetitionList.java | 3 + .../models/sqlite/SQLiteStreakList.java | 52 +++++-- .../models/sqlite/records/HabitRecord.java | 18 ++- .../sqlite/records/RepetitionRecord.java | 3 +- .../models/sqlite/records/ScoreRecord.java | 2 +- .../models/sqlite/records/StreakRecord.java | 34 ++++- .../ui/habits/edit/BaseDialogFragment.java | 3 +- .../ui/habits/edit/BaseDialogHelper.java | 31 +++-- .../edit/CreateHabitDialogFragment.java | 11 +- .../ui/habits/show/ShowHabitHelper.java | 5 +- .../ui/habits/show/views/HabitScoreView.java | 2 +- .../commands/EditHabitCommandTest.java | 6 +- .../commands/UnarchiveHabitsCommandTest.java | 2 +- .../isoron/uhabits/models/HabitFixtures.java | 9 +- .../isoron/uhabits/models/HabitListTest.java | 14 +- .../org/isoron/uhabits/models/HabitTest.java | 16 +-- .../isoron/uhabits/models/ScoreListTest.java | 3 +- .../isoron/uhabits/models/StreakListTest.java | 3 +- 42 files changed, 390 insertions(+), 334 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/models/Frequency.java diff --git a/app/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java b/app/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java index 7547691f3..6cf077513 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java +++ b/app/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java @@ -19,8 +19,7 @@ package org.isoron.uhabits; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.DateUtils; public class HabitFixtures @@ -42,8 +41,7 @@ public class HabitFixtures habit.setName("Meditate"); habit.setDescription("Did you meditate this morning?"); habit.setColor(3); - habit.setFreqNum(1); - habit.setFreqDen(1); + habit.setFrequency(Frequency.DAILY); habitList.add(habit); return habit; } @@ -51,8 +49,7 @@ public class HabitFixtures public Habit createLongHabit() { Habit habit = createEmptyHabit(); - habit.setFreqNum(3); - habit.setFreqDen(7); + habit.setFrequency(new Frequency(3, 7)); habit.setColor(4); long day = DateUtils.millisecondsInOneDay; @@ -72,8 +69,7 @@ public class HabitFixtures Habit habit = new Habit(); habit.setName("Wake up early"); habit.setDescription("Did you wake up before 6am?"); - habit.setFreqNum(2); - habit.setFreqDen(3); + habit.setFrequency(new Frequency(2, 3)); habitList.add(habit); long timestamp = DateUtils.getStartOfToday(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java b/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java index fbce70552..e19b035ff 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/io/ImportTest.java @@ -41,6 +41,7 @@ import static org.junit.Assert.*; public class ImportTest extends BaseAndroidTest { private File baseDir; + private Context context; @Before @@ -52,49 +53,40 @@ public class ImportTest extends BaseAndroidTest fixtures.purgeHabits(habitList); context = InstrumentationRegistry.getInstrumentation().getContext(); baseDir = FileUtils.getFilesDir("Backups"); - if(baseDir == null) fail("baseDir should not be null"); - } - - private void copyAssetToFile(String assetPath, File dst) throws IOException - { - InputStream in = context.getAssets().open(assetPath); - FileUtils.copy(in, dst); + if (baseDir == null) fail("baseDir should not be null"); } - private void importFromFile(String assetFilename) throws IOException + @Test + public void testHabitBullCSV() throws IOException { - File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename)); - copyAssetToFile(assetFilename, file); - assertTrue(file.exists()); - assertTrue(file.canRead()); - - GenericImporter importer = new GenericImporter(); - assertThat(importer.canHandle(file), is(true)); + importFromFile("habitbull.csv"); - importer.importHabitsFromFile(file); - } + List habits = habitList.getAll(true); + assertThat(habits.size(), equalTo(4)); - private boolean containsRepetition(Habit h, int year, int month, int day) - { - GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); - date.set(year, month - 1, day); - return h.getRepetitions().containsTimestamp(date.getTimeInMillis()); + Habit habit = habits.get(0); + assertThat(habit.getName(), equalTo("Breed dragons")); + assertThat(habit.getDescription(), equalTo("with love and fire")); + assertThat(habit.getFrequency(), equalTo(Frequency.DAILY)); + assertTrue(containsRepetition(habit, 2016, 3, 18)); + assertTrue(containsRepetition(habit, 2016, 3, 19)); + assertFalse(containsRepetition(habit, 2016, 3, 20)); } @Test - public void testTickmateDB() throws IOException + public void testLoopDB() throws IOException { - importFromFile("tickmate.db"); + importFromFile("loop.db"); List habits = habitList.getAll(true); - assertThat(habits.size(), equalTo(3)); + assertThat(habits.size(), equalTo(9)); - Habit h = habits.get(0); - assertThat(h.getName(), equalTo("Vegan")); - assertTrue(containsRepetition(h, 2016, 1, 24)); - assertTrue(containsRepetition(h, 2016, 2, 5)); - assertTrue(containsRepetition(h, 2016, 3, 18)); - assertFalse(containsRepetition(h, 2016, 3, 14)); + Habit habit = habits.get(0); + assertThat(habit.getName(), equalTo("Wake up early")); + assertThat(habit.getFrequency(), equalTo(Frequency.THREE_TIMES_PER_WEEK)); + assertTrue(containsRepetition(habit, 2016, 3, 14)); + assertTrue(containsRepetition(habit, 2016, 3, 16)); + assertFalse(containsRepetition(habit, 2016, 3, 17)); } @Test @@ -107,8 +99,8 @@ public class ImportTest extends BaseAndroidTest Habit habit = habits.get(0); assertThat(habit.getName(), equalTo("Wake up early")); - assertThat(habit.getFreqNum(), equalTo(3)); - assertThat(habit.getFreqDen(), equalTo(7)); + assertThat(habit.getFrequency(), + equalTo(Frequency.THREE_TIMES_PER_WEEK)); assertFalse(habit.hasReminder()); assertFalse(containsRepetition(habit, 2015, 12, 31)); assertTrue(containsRepetition(habit, 2016, 1, 18)); @@ -117,49 +109,58 @@ public class ImportTest extends BaseAndroidTest habit = habits.get(1); assertThat(habit.getName(), equalTo("brush teeth")); - assertThat(habit.getFreqNum(), equalTo(3)); - assertThat(habit.getFreqDen(), equalTo(7)); + assertThat(habit.getFrequency(), + equalTo(Frequency.THREE_TIMES_PER_WEEK)); assertThat(habit.hasReminder(), equalTo(true)); Reminder reminder = habit.getReminder(); assertThat(reminder.getHour(), equalTo(8)); assertThat(reminder.getMinute(), equalTo(0)); - boolean[] reminderDays = {false, true, true, true, true, true, false}; - assertThat(reminder.getDays(), equalTo(DateUtils.packWeekdayList(reminderDays))); + boolean[] reminderDays = { false, true, true, true, true, true, false }; + assertThat(reminder.getDays(), + equalTo(DateUtils.packWeekdayList(reminderDays))); } @Test - public void testHabitBullCSV() throws IOException + public void testTickmateDB() throws IOException { - importFromFile("habitbull.csv"); + importFromFile("tickmate.db"); List habits = habitList.getAll(true); - assertThat(habits.size(), equalTo(4)); + assertThat(habits.size(), equalTo(3)); - Habit habit = habits.get(0); - assertThat(habit.getName(), equalTo("Breed dragons")); - assertThat(habit.getDescription(), equalTo("with love and fire")); - assertThat(habit.getFreqNum(), equalTo(1)); - assertThat(habit.getFreqDen(), equalTo(1)); - assertTrue(containsRepetition(habit, 2016, 3, 18)); - assertTrue(containsRepetition(habit, 2016, 3, 19)); - assertFalse(containsRepetition(habit, 2016, 3, 20)); + Habit h = habits.get(0); + assertThat(h.getName(), equalTo("Vegan")); + assertTrue(containsRepetition(h, 2016, 1, 24)); + assertTrue(containsRepetition(h, 2016, 2, 5)); + assertTrue(containsRepetition(h, 2016, 3, 18)); + assertFalse(containsRepetition(h, 2016, 3, 14)); } - @Test - public void testLoopDB() throws IOException + private boolean containsRepetition(Habit h, int year, int month, int day) { - importFromFile("loop.db"); + GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); + date.set(year, month - 1, day); + return h.getRepetitions().containsTimestamp(date.getTimeInMillis()); + } - List habits = habitList.getAll(true); - assertThat(habits.size(), equalTo(9)); + private void copyAssetToFile(String assetPath, File dst) throws IOException + { + InputStream in = context.getAssets().open(assetPath); + FileUtils.copy(in, dst); + } - Habit habit = habits.get(0); - assertThat(habit.getName(), equalTo("Wake up early")); - assertThat(habit.getFreqNum(), equalTo(3)); - assertThat(habit.getFreqDen(), equalTo(7)); - assertTrue(containsRepetition(habit, 2016, 3, 14)); - assertTrue(containsRepetition(habit, 2016, 3, 16)); - assertFalse(containsRepetition(habit, 2016, 3, 17)); + private void importFromFile(String assetFilename) throws IOException + { + File file = + new File(String.format("%s/%s", baseDir.getPath(), assetFilename)); + copyAssetToFile(assetFilename, file); + assertTrue(file.exists()); + assertTrue(file.canRead()); + + GenericImporter importer = new GenericImporter(); + assertThat(importer.canHandle(file), is(true)); + + importer.importHabitsFromFile(file); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java index 6752e483b..cf8693a60 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java @@ -56,7 +56,7 @@ public class SQLiteHabitListTest extends BaseAndroidTest Habit h = new Habit(); h.setName("habit " + i); h.setId((long) i); - if (i % 2 == 0) h.setArchived(1); + if (i % 2 == 0) h.setArchived(true); HabitRecord record = new HabitRecord(); record.copyFrom(h); diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java index ee2a579d5..992c1e673 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java @@ -67,7 +67,7 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest RepetitionRecord record = getByTimestamp(today + day); assertThat(record, is(nullValue())); - Repetition rep = new Repetition(habit, today + day); + Repetition rep = new Repetition(today + day); habit.getRepetitions().add(rep); record = getByTimestamp(today + day); @@ -91,7 +91,6 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest { Repetition rep = repetitions.getByTimestamp(today); assertThat(rep, is(not(nullValue()))); - assertThat(rep.getHabit(), equalTo(habit)); assertThat(rep.getTimestamp(), equalTo(today)); rep = repetitions.getByTimestamp(today - 2 * day); @@ -103,7 +102,6 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest { Repetition rep = repetitions.getOldest(); assertThat(rep, is(not(nullValue()))); - assertThat(rep.getHabit(), equalTo(habit)); assertThat(rep.getTimestamp(), equalTo(today - 120 * day)); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java index 8011b147f..1e05d9ecc 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteScoreListTest.java @@ -92,9 +92,9 @@ public class SQLiteScoreListTest extends BaseAndroidTest new Delete().from(ScoreRecord.class).execute(); List list = new LinkedList<>(); - list.add(new Score(habit, today, 0)); - list.add(new Score(habit, today - day, 0)); - list.add(new Score(habit, today - 2 * day, 0)); + list.add(new Score(today, 0)); + list.add(new Score(today - day, 0)); + list.add(new Score(today - 2 * day, 0)); scores.add(list); diff --git a/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java b/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java index 9e3238010..fa2ca827f 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/ArchiveHabitsCommand.java @@ -47,14 +47,14 @@ public class ArchiveHabitsCommand extends Command @Override public void execute() { - for(Habit h : habits) h.setArchived(1); + for(Habit h : habits) h.setArchived(true); habitList.update(habits); } @Override public void undo() { - for(Habit h : habits) h.setArchived(0); + for(Habit h : habits) h.setArchived(false); habitList.update(habits); } diff --git a/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java b/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java index 90884e119..784d909bf 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/EditHabitCommand.java @@ -19,12 +19,10 @@ package org.isoron.uhabits.commands; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.HabitList; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; -import javax.inject.Inject; +import javax.inject.*; /** * Command to modify a habit. @@ -40,7 +38,7 @@ public class EditHabitCommand extends Command private long savedId; - private boolean hasIntervalChanged; + private boolean hasFrequencyChanged; public EditHabitCommand(Habit original, Habit modified) { @@ -53,9 +51,9 @@ public class EditHabitCommand extends Command this.modified.copyFrom(modified); this.original.copyFrom(original); - hasIntervalChanged = - (!this.original.getFreqDen().equals(this.modified.getFreqDen()) || - !this.original.getFreqNum().equals(this.modified.getFreqNum())); + Frequency originalFreq = this.original.getFrequency(); + Frequency modifiedFreq = this.modified.getFrequency(); + hasFrequencyChanged = (!originalFreq.equals(modifiedFreq)); } @Override @@ -95,7 +93,7 @@ public class EditHabitCommand extends Command private void invalidateIfNeeded(Habit habit) { - if (hasIntervalChanged) + if (hasFrequencyChanged) { habit.getCheckmarks().invalidateNewerThan(0); habit.getStreaks().invalidateNewerThan(0); diff --git a/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java b/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java index 6f06e61eb..5d442717c 100644 --- a/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java +++ b/app/src/main/java/org/isoron/uhabits/commands/UnarchiveHabitsCommand.java @@ -47,14 +47,14 @@ public class UnarchiveHabitsCommand extends Command @Override public void execute() { - for(Habit h : habits) h.setArchived(0); + for(Habit h : habits) h.setArchived(false); habitList.update(habits); } @Override public void undo() { - for(Habit h : habits) h.setArchived(1); + for(Habit h : habits) h.setArchived(true); habitList.update(habits); } diff --git a/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java b/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java index 0645fd00d..9ce0aa005 100644 --- a/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/HabitBullCSVImporter.java @@ -24,7 +24,7 @@ import android.support.annotation.NonNull; import com.activeandroid.ActiveAndroid; import com.opencsv.CSVReader; -import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.DateUtils; import java.io.BufferedReader; @@ -94,8 +94,7 @@ public class HabitBullCSVImporter extends AbstractImporter h = new Habit(); h.setName(name); h.setDescription(description); - h.setFreqDen(1); - h.setFreqNum(1); + h.setFrequency(Frequency.DAILY); habitList.add(h); habits.put(name, h); } diff --git a/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java index f91895df9..e9d1d4c83 100644 --- a/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java @@ -133,25 +133,30 @@ public class RewireDBImporter extends AbstractImporter habit.setDescription(description); int periods[] = { 7, 31, 365 }; + int numerator, denominator; switch (schedule) { case 0: - habit.setFreqNum(activeDays.split(",").length); - habit.setFreqDen(7); + numerator = activeDays.split(",").length; + denominator = 7; break; case 1: - habit.setFreqNum(days); - habit.setFreqDen(periods[periodIndex]); + numerator = days; + denominator = (periods[periodIndex]); break; case 2: - habit.setFreqNum(1); - habit.setFreqDen(repeatingCount); + numerator = 1; + denominator = repeatingCount; break; + + default: + throw new IllegalStateException(); } + habit.setFrequency(new Frequency(numerator, denominator)); habitList.add(habit); createReminder(db, habit, id); diff --git a/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java b/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java index c47c5580a..3494468ea 100644 --- a/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java +++ b/app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java @@ -23,7 +23,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; -import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.DatabaseUtils; import org.isoron.uhabits.utils.DateUtils; @@ -117,8 +117,7 @@ public class TickmateDBImporter extends AbstractImporter Habit habit = new Habit(); habit.setName(name); habit.setDescription(description); - habit.setFreqNum(1); - habit.setFreqDen(1); + habit.setFrequency(Frequency.DAILY); habitList.add(habit); createCheckmarks(db, habit, id); diff --git a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java index ccc61c194..3ae1c4b1e 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Checkmark.java +++ b/app/src/main/java/org/isoron/uhabits/models/Checkmark.java @@ -30,7 +30,7 @@ import org.apache.commons.lang3.builder.*; *

* Checkmarks are computed automatically from the list of repetitions. */ -public class Checkmark +public final class Checkmark { /** * Indicates that there was a repetition at the timestamp. 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 3f6f26ba2..701bedc64 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -189,7 +189,9 @@ public abstract class CheckmarkList if (from > to) return; - long fromExtended = from - (long) (habit.getFreqDen()) * day; + Frequency freq = habit.getFrequency(); + + long fromExtended = from - (long) (freq.getDenominator()) * day; List reps = habit.getRepetitions().getByInterval(fromExtended, to); @@ -207,10 +209,10 @@ public abstract class CheckmarkList { int counter = 0; - for (int j = 0; j < habit.getFreqDen(); j++) + for (int j = 0; j < freq.getDenominator(); j++) if (checks[i + j] == 2) counter++; - if (counter >= habit.getFreqNum()) + if (counter >= freq.getNumerator()) if (checks[i] != Checkmark.CHECKED_EXPLICITLY) checks[i] = Checkmark.CHECKED_IMPLICITLY; } diff --git a/app/src/main/java/org/isoron/uhabits/models/Frequency.java b/app/src/main/java/org/isoron/uhabits/models/Frequency.java new file mode 100644 index 000000000..5b893b5a1 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/models/Frequency.java @@ -0,0 +1,97 @@ +/* + * 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; + +import org.apache.commons.lang3.builder.*; + +/** + * Represents how often is the habit repeated. + */ +public class Frequency +{ + public static final Frequency DAILY = new Frequency(1, 1); + + public static final Frequency FIVE_TIMES_PER_WEEK = new Frequency(5, 7); + + public static final Frequency THREE_TIMES_PER_WEEK = new Frequency(3, 7); + + public static final Frequency TWO_TIMES_PER_WEEK = new Frequency(2, 7); + + public static final Frequency WEEKLY = new Frequency(1, 7); + + private final int numerator; + + private final int denominator; + + public Frequency(int numerator, int denominator) + { + if (numerator == denominator) numerator = denominator = 1; + + this.numerator = numerator; + this.denominator = denominator; + } + + @Override + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Frequency frequency = (Frequency) o; + + return new EqualsBuilder() + .append(numerator, frequency.numerator) + .append(denominator, frequency.denominator) + .isEquals(); + } + + public int getDenominator() + { + return denominator; + } + + public int getNumerator() + { + return numerator; + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(17, 37) + .append(numerator) + .append(denominator) + .toHashCode(); + } + + public double toDouble() + { + return (double) numerator / denominator; + } + + @Override + public String toString() + { + return new ToStringBuilder(this) + .append("numerator", numerator) + .append("denominator", denominator) + .toString(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 381fccaa4..bc85f85c2 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -47,19 +47,13 @@ public class Habit private String description; @NonNull - private Integer freqNum; - - @NonNull - private Integer freqDen; + private Frequency frequency; @NonNull private Integer color; @NonNull - private Integer highlight; - - @NonNull - private Integer archived; + private boolean archived; @NonNull private StreakList streaks; @@ -109,10 +103,8 @@ public class Habit HabitsApplication.getComponent().inject(this); this.color = 5; - this.highlight = 0; - this.archived = 0; - this.freqDen = 7; - this.freqNum = 3; + this.archived = false; + this.frequency = new Frequency(3, 7); checkmarks = factory.buildCheckmarkList(this); streaks = factory.buildStreakList(this); @@ -139,24 +131,13 @@ public class Habit { this.name = model.getName(); this.description = model.getDescription(); - this.freqNum = model.getFreqNum(); - this.freqDen = model.getFreqDen(); this.color = model.getColor(); + this.archived = model.isArchived(); + this.frequency = model.frequency; this.reminder = model.reminder; - this.highlight = model.getHighlight(); - this.archived = model.getArchived(); observable.notifyListeners(); } - /** - * Flag that indicates whether the habit is archived. Archived habits are - * usually omitted from listings, unless explicitly included. - */ - public Integer getArchived() - { - return archived; - } - /** * List of checkmarks belonging to this habit. */ @@ -179,21 +160,11 @@ public class Habit return color; } - @NonNull - public Reminder getReminder() - { - if(reminder == null) throw new IllegalStateException(); - return reminder; - } - public void setColor(Integer color) { this.color = color; } - /** - * Description of the habit - */ public String getDescription() { return description; @@ -204,46 +175,15 @@ public class Habit this.description = description; } - /** - * Frequency denominator. If a habit is performed 3 times in 7 days, this - * field equals 7. - */ - public Integer getFreqDen() - { - return freqDen; - } - - public void setFreqDen(Integer freqDen) - { - this.freqDen = freqDen; - } - - /** - * Frequency numerator. If a habit is performed 3 times in 7 days, this - * field equals 3. - */ - public Integer getFreqNum() - { - return freqNum; - } - - public void setFreqNum(@NonNull Integer freqNum) - { - this.freqNum = freqNum; - } - - /** - * Not currently used. - */ @NonNull - public Integer getHighlight() + public Frequency getFrequency() { - return highlight; + return frequency; } - public void setHighlight(@NonNull Integer highlight) + public void setFrequency(@NonNull Frequency frequency) { - this.highlight = highlight; + this.frequency = frequency; } @Nullable @@ -257,9 +197,6 @@ public class Habit this.id = id; } - /** - * Name of the habit - */ @NonNull public String getName() { @@ -277,26 +214,39 @@ public class Habit } /** - * List of repetitions belonging to this habit. + * Returns the reminder for this habit. + *

+ * Before calling this method, you should call {@link #hasReminder()} to + * verify that a reminder does exist, otherwise an exception will be + * thrown. + * + * @return the reminder for this habit + * @throws IllegalStateException if habit has no reminder */ + @NonNull + public Reminder getReminder() + { + if (reminder == null) throw new IllegalStateException(); + return reminder; + } + + public void setReminder(@Nullable Reminder reminder) + { + this.reminder = reminder; + } + @NonNull public RepetitionList getRepetitions() { return repetitions; } - /** - * List of scores belonging to this habit. - */ @NonNull public ScoreList getScores() { return scores; } - /** - * List of streaks belonging to this habit. - */ @NonNull public StreakList getStreaks() { @@ -315,7 +265,7 @@ public class Habit } /** - * Checks whether the habit has a reminder set. + * Returns whether the habit has a reminder. * * @return true if habit has reminder, false otherwise */ @@ -324,26 +274,16 @@ public class Habit return reminder != null; } - /** - * Returns whether the habit is archived or not. - * - * @return true if archived - */ public boolean isArchived() { - return archived != 0; + return archived; } - public void setArchived(@NonNull Integer archived) + public void setArchived(boolean archived) { this.archived = archived; } - public void setReminder(@Nullable Reminder reminder) - { - this.reminder = reminder; - } - @Override public String toString() { @@ -351,10 +291,7 @@ public class Habit .append("id", id) .append("name", name) .append("description", description) - .append("freqNum", freqNum) - .append("freqDen", freqDen) .append("color", color) - .append("highlight", highlight) .append("archived", archived) .toString(); } diff --git a/app/src/main/java/org/isoron/uhabits/models/HabitList.java b/app/src/main/java/org/isoron/uhabits/models/HabitList.java index 4b80e7383..4bd211e7e 100644 --- a/app/src/main/java/org/isoron/uhabits/models/HabitList.java +++ b/app/src/main/java/org/isoron/uhabits/models/HabitList.java @@ -208,12 +208,14 @@ public abstract class HabitList for (Habit habit : getAll(true)) { + Frequency freq = habit.getFrequency(); + String[] cols = { String.format("%03d", indexOf(habit) + 1), habit.getName(), habit.getDescription(), - Integer.toString(habit.getFreqNum()), - Integer.toString(habit.getFreqDen()), + Integer.toString(freq.getNumerator()), + Integer.toString(freq.getDenominator()), ColorUtils.CSV_PALETTE[habit.getColor()] }; diff --git a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java index 001bf2695..a762b5f8a 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java +++ b/app/src/main/java/org/isoron/uhabits/models/ModelObservable.java @@ -27,7 +27,7 @@ import java.util.*; */ public class ModelObservable { - List listeners; + private List listeners; /** * Creates a new ModelObservable with no listeners. @@ -62,7 +62,7 @@ public class ModelObservable * Removes the given listener. *

* The listener will no longer be notified when the model changes. If the - * given listener is not subscrined to this observable, does nothing. + * given listener is not subscribed to this observable, does nothing. * * @param l the listener to be removed */ @@ -77,6 +77,10 @@ public class ModelObservable */ public interface Listener { + /** + * Called whenever the model associated to this observable has been + * modified. + */ void onModelChange(); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/Reminder.java b/app/src/main/java/org/isoron/uhabits/models/Reminder.java index e92bef33a..2377113c3 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Reminder.java +++ b/app/src/main/java/org/isoron/uhabits/models/Reminder.java @@ -19,7 +19,7 @@ package org.isoron.uhabits.models; -public class Reminder +public final class Reminder { private final int hour; diff --git a/app/src/main/java/org/isoron/uhabits/models/Repetition.java b/app/src/main/java/org/isoron/uhabits/models/Repetition.java index bcfb8bc65..72e378205 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Repetition.java +++ b/app/src/main/java/org/isoron/uhabits/models/Repetition.java @@ -19,18 +19,14 @@ package org.isoron.uhabits.models; -import android.support.annotation.*; - import org.apache.commons.lang3.builder.*; /** * Represents a record that the user has performed a certain habit at a certain * date. */ -public class Repetition +public final class Repetition { - @NonNull - private final Habit habit; private final long timestamp; @@ -40,20 +36,13 @@ public class Repetition * The timestamp corresponds to the days this repetition occurred. Time of * day must be midnight (UTC). * - * @param habit the habit to which this repetition belongs. * @param timestamp the time this repetition occurred. */ - public Repetition(Habit habit, long timestamp) + public Repetition(long timestamp) { - this.habit = habit; this.timestamp = timestamp; } - public Habit getHabit() - { - return habit; - } - public long getTimestamp() { return timestamp; diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index d4f38e5e3..23866b939 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -21,7 +21,6 @@ package org.isoron.uhabits.models; import android.support.annotation.*; -import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.*; import java.util.*; @@ -183,7 +182,7 @@ public abstract class RepetitionList if (rep != null) remove(rep); else { - rep = new Repetition(habit, timestamp); + rep = new Repetition(timestamp); add(rep); } diff --git a/app/src/main/java/org/isoron/uhabits/models/Score.java b/app/src/main/java/org/isoron/uhabits/models/Score.java index e4f67c34c..c9ebdea14 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Score.java +++ b/app/src/main/java/org/isoron/uhabits/models/Score.java @@ -24,18 +24,13 @@ import org.apache.commons.lang3.builder.*; /** * Represents how strong a habit is at a certain date. */ -public class Score +public final class Score { /** * Maximum score value attainable by any habit. */ public static final int MAX_VALUE = 19259478; - /** - * Habit to which this score belongs to. - */ - private Habit habit; - /** * Timestamp of the day to which this score applies. Time of day should be * midnight (UTC). @@ -47,9 +42,8 @@ public class Score */ private final Integer value; - public Score(Habit habit, Long timestamp, Integer value) + public Score(Long timestamp, Integer value) { - this.habit = habit; this.timestamp = timestamp; this.value = value; } @@ -91,11 +85,6 @@ public class Score return Long.signum(this.getTimestamp() - other.getTimestamp()); } - public Habit getHabit() - { - return habit; - } - public Long getTimestamp() { return timestamp; diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index 983e6c11d..a8d797233 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -157,7 +157,7 @@ public abstract class ScoreList implements Iterable protected void compute(long from, long to) { final long day = DateUtils.millisecondsInOneDay; - final double freq = ((double) habit.getFreqNum()) / habit.getFreqDen(); + final double freq = habit.getFrequency().toDouble(); int newestValue = 0; long newestTimestamp = 0; @@ -181,7 +181,7 @@ public abstract class ScoreList implements Iterable { int value = checkmarkValues[checkmarkValues.length - i - 1]; lastScore = Score.compute(freq, lastScore, value); - scores.add(new Score(habit, beginning + day * i, lastScore)); + scores.add(new Score(beginning + day * i, lastScore)); } add(scores); @@ -241,7 +241,7 @@ public abstract class ScoreList implements Iterable for (Long v : groupValues) meanValue += v; meanValue /= groupValues.size(); - scores.add(new Score(habit, timestamp, (int) meanValue)); + scores.add(new Score(timestamp, (int) meanValue)); } return scores; diff --git a/app/src/main/java/org/isoron/uhabits/models/Streak.java b/app/src/main/java/org/isoron/uhabits/models/Streak.java index 107e5fe86..5a7a59f3d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Streak.java +++ b/app/src/main/java/org/isoron/uhabits/models/Streak.java @@ -22,17 +22,14 @@ package org.isoron.uhabits.models; import org.apache.commons.lang3.builder.*; import org.isoron.uhabits.utils.*; -public class Streak +public final class Streak { - private Habit habit; + private final long start; - private long start; + private final long end; - private long end; - - public Streak(Habit habit, long start, long end) + public Streak(long start, long end) { - this.habit = habit; this.start = start; this.end = end; } @@ -55,11 +52,6 @@ public class Streak return end; } - public Habit getHabit() - { - return habit; - } - public long getLength() { return (end - start) / DateUtils.millisecondsInOneDay + 1; diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java index c26b3e284..4ae6a2b55 100644 --- a/app/src/main/java/org/isoron/uhabits/models/StreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java @@ -97,7 +97,7 @@ public abstract class StreakList { long start = transitions.get(i); long end = transitions.get(i + 1); - streaks.add(new Streak(habit, start, end)); + streaks.add(new Streak(start, end)); } return streaks; diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java index 595caf444..2ce44ceb4 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java @@ -57,8 +57,11 @@ public class SQLiteRepetitionList extends RepetitionList @Override public void add(Repetition rep) { + check(habit.getId()); + RepetitionRecord record = new RepetitionRecord(); record.copyFrom(rep); + record.habit = habitRecord; record.save(); observable.notifyListeners(); } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java index 322fd60e8..8cf10b49f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteStreakList.java @@ -20,12 +20,14 @@ package org.isoron.uhabits.models.sqlite; import android.support.annotation.*; +import android.support.annotation.Nullable; 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.*; @@ -34,21 +36,29 @@ import java.util.*; */ public class SQLiteStreakList extends StreakList { + private HabitRecord habitRecord; + + @NonNull + private final SQLiteUtils sqlite; + public SQLiteStreakList(Habit habit) { super(habit); + sqlite = new SQLiteUtils<>(StreakRecord.class); } @Override public List getAll() { + check(habit.getId()); rebuild(); - List records = new Select() - .from(StreakRecord.class) - .where("habit = ?", habit.getId()) - .orderBy("end desc") - .execute(); + String query = StreakRecord.SELECT + "where habit = ? " + + "order by end desc"; + + String params[] = { Long.toString(habit.getId())}; + + List records = sqlite.query(query, params); return recordsToStreaks(records); } @@ -75,11 +85,14 @@ public class SQLiteStreakList extends StreakList @Override protected void add(@NonNull List streaks) { + check(habit.getId()); + DatabaseUtils.executeAsTransaction(() -> { for (Streak streak : streaks) { StreakRecord record = new StreakRecord(); record.copyFrom(streak); + record.habit = habitRecord; record.save(); } }); @@ -95,12 +108,15 @@ public class SQLiteStreakList extends StreakList @Nullable private StreakRecord getNewestRecord() { - return new Select() - .from(StreakRecord.class) - .where("habit = ?", habit.getId()) - .orderBy("end desc") - .limit(1) - .executeSingle(); + check(habit.getId()); + String query = StreakRecord.SELECT + "where habit = ? " + + "order by end desc " + + "limit 1 "; + String params[] = { habit.getId().toString() }; + StreakRecord record = sqlite.querySingle(query, params); + if (record != null) record.habit = habitRecord; + return record; + } @NonNull @@ -109,8 +125,22 @@ public class SQLiteStreakList extends StreakList LinkedList streaks = new LinkedList<>(); for (StreakRecord record : records) + { + record.habit = habitRecord; streaks.add(record.toStreak()); + } return streaks; } + + @Contract("null -> fail") + private void check(Long id) + { + if (id == null) throw new RuntimeException("habit is not saved"); + + if(habitRecord != null) return; + + habitRecord = HabitRecord.get(id); + if (habitRecord == null) throw new RuntimeException("habit not found"); + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java index 738232756..154312264 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java @@ -142,9 +142,13 @@ public class HabitRecord extends Model implements SQLiteRecord { this.name = model.getName(); this.description = model.getDescription(); - this.freqNum = model.getFreqNum(); - this.freqDen = model.getFreqDen(); + this.highlight = 0; this.color = model.getColor(); + this.archived = model.isArchived() ? 1 : 0; + Frequency freq = model.getFrequency(); + this.freqNum = freq.getNumerator(); + this.freqDen = freq.getDenominator(); + if(model.hasReminder()) { Reminder reminder = model.getReminder(); @@ -152,8 +156,6 @@ public class HabitRecord extends Model implements SQLiteRecord this.reminderMin = reminder.getMinute(); this.reminderDays = reminder.getDays(); } - this.highlight = model.getHighlight(); - this.archived = model.getArchived(); } @Override @@ -177,11 +179,9 @@ public class HabitRecord extends Model implements SQLiteRecord { habit.setName(this.name); habit.setDescription(this.description); - habit.setFreqNum(this.freqNum); - habit.setFreqDen(this.freqDen); + habit.setFrequency(new Frequency(this.freqNum, this.freqDen)); habit.setColor(this.color); - habit.setHighlight(this.highlight); - habit.setArchived(this.archived); + habit.setArchived(this.archived != 0); habit.setId(this.getId()); if (reminderHour != null && reminderMin != null) @@ -204,8 +204,6 @@ public class HabitRecord extends Model implements SQLiteRecord private void setId(Long id) { - // HACK: The id field is declared private by ActiveAndroid and - // there are no setters. (WTF?) try { Field f = (Model.class).getDeclaredField("mId"); diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java index d2a7667ec..eec93777f 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java @@ -46,7 +46,6 @@ public class RepetitionRecord extends Model implements SQLiteRecord public void copyFrom(Repetition repetition) { - habit = HabitRecord.get(repetition.getHabit().getId()); timestamp = repetition.getTimestamp(); } @@ -60,6 +59,6 @@ public class RepetitionRecord extends Model implements SQLiteRecord { SQLiteHabitList habitList = SQLiteHabitList.getInstance(); Habit h = habitList.getById(habit.getId()); - return new Repetition(h, timestamp); + return new Repetition(timestamp); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java index 24bc65509..09d8602a2 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/ScoreRecord.java @@ -65,6 +65,6 @@ public class ScoreRecord extends Model implements SQLiteRecord { SQLiteHabitList habitList = SQLiteHabitList.getInstance(); Habit h = habitList.getById(habit.getId()); - return new Score(h, timestamp, score); + return new Score(timestamp, score); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/StreakRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/StreakRecord.java index c1b4b0a56..90b4362f2 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/StreakRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/StreakRecord.java @@ -19,18 +19,24 @@ package org.isoron.uhabits.models.sqlite.records; +import android.database.*; + import com.activeandroid.*; import com.activeandroid.annotation.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.*; +import java.lang.reflect.*; + /** * The SQLite database record corresponding to a Streak. */ @Table(name = "Streak") -public class StreakRecord extends Model +public class StreakRecord extends Model implements SQLiteRecord { + public static final String SELECT = "select id, start, end, length from Streak "; + @Column(name = "habit") public HabitRecord habit; @@ -50,16 +56,38 @@ public class StreakRecord extends Model public void copyFrom(Streak streak) { - habit = HabitRecord.get(streak.getHabit().getId()); start = streak.getStart(); end = streak.getEnd(); length = streak.getLength(); } + @Override + public void copyFrom(Cursor c) + { + setId(c.getLong(0)); + start = c.getLong(1); + end = c.getLong(2); + length = c.getLong(3); + } + + private void setId(long id) + { + try + { + Field f = (Model.class).getDeclaredField("mId"); + f.setAccessible(true); + f.set(this, id); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + public Streak toStreak() { SQLiteHabitList habitList = SQLiteHabitList.getInstance(); Habit h = habitList.getById(habit.getId()); - return new Streak(h, start, end); + return new Streak(start, end); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java index d0ee67b85..8b597694f 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogFragment.java @@ -83,8 +83,7 @@ public abstract class BaseDialogFragment extends AppCompatDialogFragment if (position < 0 || position > 4) throw new IllegalArgumentException(); int freqNums[] = { 1, 1, 2, 5, 3 }; int freqDens[] = { 1, 7, 7, 7, 7 }; - modifiedHabit.setFreqNum(freqNums[position]); - modifiedHabit.setFreqDen(freqDens[position]); + modifiedHabit.setFrequency(new Frequency(freqNums[position], freqDens[position])); helper.populateFrequencyFields(modifiedHabit); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java index 93790f403..3cad2ae8d 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/BaseDialogHelper.java @@ -84,8 +84,12 @@ public class BaseDialogHelper habit.setDescription(tvDescription.getText().toString().trim()); String freqNum = tvFreqNum.getText().toString(); String freqDen = tvFreqDen.getText().toString(); - if (!freqNum.isEmpty()) habit.setFreqNum(Integer.parseInt(freqNum)); - if (!freqDen.isEmpty()) habit.setFreqDen(Integer.parseInt(freqDen)); + if (!freqNum.isEmpty() && !freqDen.isEmpty()) + { + int numerator = Integer.parseInt(freqNum); + int denominator = Integer.parseInt(freqDen); + habit.setFrequency(new Frequency(numerator, denominator)); + } } void populateColor(int paletteColor) @@ -99,16 +103,18 @@ public class BaseDialogHelper { int quickSelectPosition = -1; - if (habit.getFreqNum().equals(habit.getFreqDen())) + Frequency freq = habit.getFrequency(); + + if (freq.equals(Frequency.DAILY)) quickSelectPosition = 0; - else if (habit.getFreqNum() == 1 && habit.getFreqDen() == 7) + else if (freq.equals(Frequency.WEEKLY)) quickSelectPosition = 1; - else if (habit.getFreqNum() == 2 && habit.getFreqDen() == 7) + else if (freq.equals(Frequency.TWO_TIMES_PER_WEEK)) quickSelectPosition = 2; - else if (habit.getFreqNum() == 5 && habit.getFreqDen() == 7) + else if (freq.equals(Frequency.FIVE_TIMES_PER_WEEK)) quickSelectPosition = 3; if (quickSelectPosition >= 0) @@ -116,8 +122,8 @@ public class BaseDialogHelper else showCustomFrequency(); - tvFreqNum.setText(habit.getFreqNum().toString()); - tvFreqDen.setText(habit.getFreqDen().toString()); + tvFreqNum.setText(Integer.toString(freq.getNumerator())); + tvFreqDen.setText(Integer.toString(freq.getDenominator())); } @SuppressWarnings("ConstantConditions") @@ -138,8 +144,7 @@ public class BaseDialogHelper tvReminderTime.setText(time); llReminderDays.setVisibility(View.VISIBLE); - boolean weekdays[] = - DateUtils.unpackWeekdayList(reminder.getDays()); + boolean weekdays[] = DateUtils.unpackWeekdayList(reminder.getDays()); tvReminderDays.setText( DateUtils.formatWeekdayList(frag.getContext(), weekdays)); } @@ -169,14 +174,16 @@ public class BaseDialogHelper valid = false; } - if (habit.getFreqNum() <= 0) + Frequency freq = habit.getFrequency(); + + if (freq.getNumerator() <= 0) { tvFreqNum.setError( frag.getString(R.string.validation_number_should_be_positive)); valid = false; } - if (habit.getFreqNum() > habit.getFreqDen()) + if (freq.getNumerator() > freq.getDenominator()) { tvFreqNum.setError( frag.getString(R.string.validation_at_most_one_rep_per_day)); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java index 777c32f7c..28188964d 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/CreateHabitDialogFragment.java @@ -19,11 +19,9 @@ package org.isoron.uhabits.ui.habits.edit; -import org.isoron.uhabits.HabitsApplication; -import org.isoron.uhabits.R; -import org.isoron.uhabits.commands.Command; -import org.isoron.uhabits.commands.CreateHabitCommand; -import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; public class CreateHabitDialogFragment extends BaseDialogFragment { @@ -37,8 +35,7 @@ public class CreateHabitDialogFragment extends BaseDialogFragment protected void initializeHabits() { modifiedHabit = new Habit(); - modifiedHabit.setFreqNum(1); - modifiedHabit.setFreqDen(1); + modifiedHabit.setFrequency(Frequency.DAILY); modifiedHabit.setColor( prefs.getDefaultHabitColor(modifiedHabit.getColor())); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java index d6a53096a..8539f5792 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -42,8 +42,9 @@ public class ShowHabitHelper if (fragment.habit == null) return ""; Resources resources = fragment.getResources(); - Integer freqNum = fragment.habit.getFreqNum(); - Integer freqDen = fragment.habit.getFreqDen(); + Frequency freq = fragment.habit.getFrequency(); + Integer freqNum = freq.getNumerator(); + Integer freqDen = freq.getDenominator(); if (freqNum.equals(freqDen)) return resources.getString(R.string.every_day); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java index 2d125283a..b162b9786 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java @@ -425,7 +425,7 @@ public class HabitScoreView extends ScrollableDataView int step = Score.MAX_VALUE / 10; int current = previous + random.nextInt(step * 2) - step; current = Math.max(0, Math.min(Score.MAX_VALUE, current)); - scores.add(new Score(habit, timestamp, current)); + scores.add(new Score(timestamp, current)); previous = current; timestamp -= day; } diff --git a/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java index 8f7fc1a1c..b4f09036a 100644 --- a/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/EditHabitCommandTest.java @@ -43,8 +43,7 @@ public class EditHabitCommandTest extends BaseUnitTest habit = fixtures.createShortHabit(); habit.setName("original"); - habit.setFreqDen(1); - habit.setFreqNum(1); + habit.setFrequency(Frequency.DAILY); modified = new Habit(); modified.copyFrom(habit); @@ -75,8 +74,7 @@ public class EditHabitCommandTest extends BaseUnitTest @Test public void testExecuteUndoRedo_withModifiedInterval() { - modified.setFreqNum(1); - modified.setFreqDen(7); + modified.setFrequency(Frequency.TWO_TIMES_PER_WEEK); command = new EditHabitCommand(habit, modified); int originalScore = habit.getScores().getTodayValue(); diff --git a/app/src/test/java/org/isoron/uhabits/commands/UnarchiveHabitsCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/UnarchiveHabitsCommandTest.java index 1c952e663..1d5eddd73 100644 --- a/app/src/test/java/org/isoron/uhabits/commands/UnarchiveHabitsCommandTest.java +++ b/app/src/test/java/org/isoron/uhabits/commands/UnarchiveHabitsCommandTest.java @@ -39,7 +39,7 @@ public class UnarchiveHabitsCommandTest extends BaseUnitTest super.setUp(); habit = fixtures.createShortHabit(); - habit.setArchived(1); + habit.setArchived(true); habitList.update(habit); command = new UnarchiveHabitsCommand(Collections.singletonList(habit)); diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitFixtures.java b/app/src/test/java/org/isoron/uhabits/models/HabitFixtures.java index 70f7f1131..0ff459c30 100644 --- a/app/src/test/java/org/isoron/uhabits/models/HabitFixtures.java +++ b/app/src/test/java/org/isoron/uhabits/models/HabitFixtures.java @@ -40,8 +40,7 @@ public class HabitFixtures habit.setName("Meditate"); habit.setDescription("Did you meditate this morning?"); habit.setColor(3); - habit.setFreqNum(1); - habit.setFreqDen(1); + habit.setFrequency(Frequency.DAILY); habitList.add(habit); return habit; } @@ -49,8 +48,7 @@ public class HabitFixtures public Habit createLongHabit() { Habit habit = createEmptyHabit(); - habit.setFreqNum(3); - habit.setFreqDen(7); + habit.setFrequency(new Frequency(3, 7)); habit.setColor(4); long day = DateUtils.millisecondsInOneDay; @@ -70,8 +68,7 @@ public class HabitFixtures Habit habit = new Habit(); habit.setName("Wake up early"); habit.setDescription("Did you wake up before 6am?"); - habit.setFreqNum(2); - habit.setFreqDen(3); + habit.setFrequency(new Frequency(2, 3)); habitList.add(habit); long timestamp = DateUtils.getStartOfToday(); diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java b/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java index 7175819a6..8d0e4590c 100644 --- a/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/HabitListTest.java @@ -57,10 +57,10 @@ public class HabitListTest extends BaseUnitTest habit.setReminder(new Reminder(8, 30, DateUtils.ALL_WEEK_DAYS)); } - habits.get(0).setArchived(1); - habits.get(1).setArchived(1); - habits.get(4).setArchived(1); - habits.get(7).setArchived(1); + habits.get(0).setArchived(true); + habits.get(1).setArchived(true); + habits.get(4).setArchived(true); + habits.get(7).setArchived(true); } @Test @@ -166,15 +166,13 @@ public class HabitListTest extends BaseUnitTest Habit h1 = new Habit(); h1.setName("Meditate"); h1.setDescription("Did you meditate this morning?"); - h1.setFreqNum(1); - h1.setFreqDen(1); + h1.setFrequency(Frequency.DAILY); h1.setColor(3); Habit h2 = new Habit(); h2.setName("Wake up early"); h2.setDescription("Did you wake up before 6am?"); - h2.setFreqNum(2); - h2.setFreqDen(3); + h2.setFrequency(new Frequency(2, 3)); h2.setColor(5); list.add(h1); diff --git a/app/src/test/java/org/isoron/uhabits/models/HabitTest.java b/app/src/test/java/org/isoron/uhabits/models/HabitTest.java index 5bc1b1f26..5b39641db 100644 --- a/app/src/test/java/org/isoron/uhabits/models/HabitTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/HabitTest.java @@ -27,6 +27,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; public class HabitTest extends BaseUnitTest @@ -36,8 +37,7 @@ public class HabitTest extends BaseUnitTest public void testConstructor_default() { Habit habit = new Habit(); - assertThat(habit.getArchived(), is(0)); - assertThat(habit.getHighlight(), is(0)); + assertFalse(habit.isArchived()); assertThat(habit.hasReminder(), is(false)); assertThat(habit.getStreaks(), is(not(nullValue()))); @@ -50,20 +50,16 @@ public class HabitTest extends BaseUnitTest public void test_copyAttributes() { Habit model = new Habit(); - model.setArchived(1); - model.setHighlight(1); + model.setArchived(true); model.setColor(0); - model.setFreqNum(10); - model.setFreqDen(20); + model.setFrequency(new Frequency(10, 20)); model.setReminder(new Reminder(8, 30, 1)); Habit habit = new Habit(); habit.copyFrom(model); - assertThat(habit.getArchived(), is(model.getArchived())); - assertThat(habit.getHighlight(), is(model.getHighlight())); + assertThat(habit.isArchived(), is(model.isArchived())); assertThat(habit.getColor(), is(model.getColor())); - assertThat(habit.getFreqNum(), is(model.getFreqNum())); - assertThat(habit.getFreqDen(), is(model.getFreqDen())); + assertThat(habit.getFrequency(), equalTo(model.getFrequency())); assertThat(habit.getReminder(), equalTo(model.getReminder())); } diff --git a/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java b/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java index 8b1007575..489506e61 100644 --- a/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java @@ -147,8 +147,7 @@ public class ScoreListTest extends BaseUnitTest toggleRepetitions(0, 2); assertThat(habit.getScores().getTodayValue(), equalTo(1948077)); - habit.setFreqNum(1); - habit.setFreqDen(2); + habit.setFrequency(new Frequency(1, 2)); habit.getScores().invalidateNewerThan(0); assertThat(habit.getScores().getTodayValue(), equalTo(1974654)); diff --git a/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java b/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java index a4253d543..6ca9a3b5d 100644 --- a/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java +++ b/app/src/test/java/org/isoron/uhabits/models/StreakListTest.java @@ -46,8 +46,7 @@ public class StreakListTest extends BaseUnitTest { super.setUp(); habit = fixtures.createLongHabit(); - habit.setFreqDen(1); - habit.setFreqNum(1); + habit.setFrequency(Frequency.DAILY); streaks = habit.getStreaks(); streaks.rebuild(); From a060cbe578f9079dcc6ab1729394b6e1797dc3f6 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 22 Jun 2016 08:48:08 -0400 Subject: [PATCH 033/184] ShowHabit: Refactor subtitle and overview --- .../org/isoron/uhabits/ui/BaseRootView.java | 4 +- .../org/isoron/uhabits/ui/BaseScreen.java | 8 +- .../ui/habits/show/ShowHabitFragment.java | 97 +++------ .../ui/habits/show/ShowHabitHelper.java | 101 ---------- .../ui/habits/show/views/HabitDataView.java | 2 - .../habits/show/views/OverviewCardView.java | 186 ++++++++++++++++++ .../habits/show/views/SubtitleCardView.java | 153 ++++++++++++++ .../org/isoron/uhabits/utils/ColorUtils.java | 8 +- .../main/res/layout/list_habits_preview.xml | 12 +- app/src/main/res/layout/show_habit.xml | 179 +---------------- .../main/res/layout/show_habit_overview.xml | 109 ++++++++++ .../main/res/layout/show_habit_preview.xml | 54 +++++ .../main/res/layout/show_habit_subtitle.xml | 79 ++++++++ app/src/main/res/values/styles_show_habit.xml | 37 ++++ 14 files changed, 667 insertions(+), 362 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCardView.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCardView.java create mode 100644 app/src/main/res/layout/show_habit_overview.xml create mode 100644 app/src/main/res/layout/show_habit_preview.xml create mode 100644 app/src/main/res/layout/show_habit_subtitle.xml create mode 100644 app/src/main/res/values/styles_show_habit.xml diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java b/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java index 4016442ab..eb9891f95 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseRootView.java @@ -96,8 +96,8 @@ public abstract class BaseRootView extends FrameLayout View view = findViewById(R.id.toolbarShadow); if (view != null) view.setVisibility(View.GONE); - view = findViewById(R.id.headerShadow); - if (view != null) view.setVisibility(View.GONE); +// view = findViewById(R.id.headerShadow); +// if (view != null) view.setVisibility(View.GONE); } protected void initToolbar() diff --git a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java index bd803cc4a..00e4aee07 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java +++ b/app/src/main/java/org/isoron/uhabits/ui/BaseScreen.java @@ -89,8 +89,8 @@ public abstract class BaseScreen View view = activity.findViewById(R.id.toolbarShadow); if (view != null) view.setVisibility(View.GONE); - view = activity.findViewById(R.id.headerShadow); - if (view != null) view.setVisibility(View.GONE); +// view = activity.findViewById(R.id.headerShadow); +// if (view != null) view.setVisibility(View.GONE); } } @@ -270,8 +270,8 @@ public abstract class BaseScreen View view = activity.findViewById(R.id.toolbarShadow); if (view != null) view.setVisibility(View.GONE); - view = activity.findViewById(R.id.headerShadow); - if (view != null) view.setVisibility(View.GONE); +// view = activity.findViewById(R.id.headerShadow); +// if (view != null) view.setVisibility(View.GONE); } private class ActionModeWrapper implements ActionMode.Callback diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 56279b767..9352e58d3 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -19,38 +19,20 @@ package org.isoron.uhabits.ui.habits.show; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.Spinner; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.ModelObservable; -import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.ui.habits.edit.BaseDialogFragment; -import org.isoron.uhabits.ui.habits.edit.EditHabitDialogFragment; -import org.isoron.uhabits.ui.habits.edit.HistoryEditorDialog; -import org.isoron.uhabits.utils.DateUtils; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.HabitFrequencyView; -import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView; -import org.isoron.uhabits.ui.habits.show.views.HabitScoreView; -import org.isoron.uhabits.ui.habits.show.views.HabitStreakView; - -import java.util.LinkedList; -import java.util.List; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; +import android.os.*; +import android.support.v4.app.*; +import android.view.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.edit.*; +import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.utils.*; + +import java.util.*; + +import butterknife.*; public class ShowHabitFragment extends Fragment implements ModelObservable.Listener @@ -90,6 +72,12 @@ public class ShowHabitFragment extends Fragment @BindView(R.id.streakView) HabitStreakView habitStreakView; + @BindView(R.id.subtitle) + SubtitleCardView subtitleView; + + @BindView(R.id.overview) + OverviewCardView overview; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -103,7 +91,6 @@ public class ShowHabitFragment extends Fragment habit = activity.getHabit(); helper.updateColors(); - helper.updateMainHeader(view); int defaultScoreInterval = InterfaceUtils.getDefaultScoreInterval(getContext()); @@ -131,6 +118,9 @@ public class ShowHabitFragment extends Fragment private void createDataViews() { + subtitleView.setHabit(habit); + overview.setHabit(habit); + dataViews = new LinkedList<>(); dataViews.add(habitScoreView); dataViews.add(habitHistoryView); @@ -141,13 +131,6 @@ public class ShowHabitFragment extends Fragment dataView.setHabit(habit); } - @Override - public void onResume() - { - super.onResume(); - refreshData(); - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @@ -173,11 +156,6 @@ public class ShowHabitFragment extends Fragment return true; } - public void refreshData() - { - new RefreshTask().execute(); - } - private void setScoreBucketSize(int position) { if (getView() == null) return; @@ -185,8 +163,6 @@ public class ShowHabitFragment extends Fragment habitScoreView.setBucketSize( HabitScoreView.DEFAULT_BUCKET_SIZES[position]); - if (position != previousScoreInterval) refreshData(); - InterfaceUtils.setDefaultScoreInterval(getContext(), position); previousScoreInterval = position; } @@ -194,10 +170,8 @@ public class ShowHabitFragment extends Fragment @Override public void onModelChange() { - refreshData(); activity.runOnUiThread(() -> { helper.updateColors(); - helper.updateMainHeader(getView()); helper.updateCardHeaders(getView()); if (activity != null) activity.setupHabitActionBar(); }); @@ -217,31 +191,6 @@ public class ShowHabitFragment extends Fragment super.onPause(); } - private class RefreshTask extends BaseTask - { - @Override - protected void doInBackground() - { - if (habit == null) return; - if (dataViews == null) return; - - long today = DateUtils.getStartOfToday(); - long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; - long lastYear = today - 365 * DateUtils.millisecondsInOneDay; - - todayScore = (float) habit.getScores().getTodayValue(); - lastMonthScore = (float) habit.getScores().getValue(lastMonth); - lastYearScore = (float) habit.getScores().getValue(lastYear); - } - - @Override - protected void onPostExecute(Void aVoid) - { - helper.updateScore(getView()); - super.onPostExecute(aVoid); - } - } - private class OnItemSelectedListener implements AdapterView.OnItemSelectedListener { diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java index 8539f5792..168355e21 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -19,13 +19,10 @@ package org.isoron.uhabits.ui.habits.show; -import android.content.res.*; import android.view.*; import android.widget.*; import org.isoron.uhabits.*; -import org.isoron.uhabits.models.*; -import org.isoron.uhabits.ui.habits.show.views.*; import org.isoron.uhabits.utils.*; public class ShowHabitHelper @@ -37,35 +34,9 @@ public class ShowHabitHelper this.fragment = fragment; } - String getFreqText() - { - if (fragment.habit == null) return ""; - - Resources resources = fragment.getResources(); - Frequency freq = fragment.habit.getFrequency(); - Integer freqNum = freq.getNumerator(); - Integer freqDen = freq.getDenominator(); - - if (freqNum.equals(freqDen)) - return resources.getString(R.string.every_day); - - if (freqNum == 1) - { - if (freqDen == 7) return resources.getString(R.string.every_week); - if (freqDen % 7 == 0) - return resources.getString(R.string.every_x_weeks, freqDen / 7); - return resources.getString(R.string.every_x_days, freqDen); - } - - String times_every = resources.getString(R.string.time_every); - return String.format("%d %s %d %s", freqNum, times_every, freqDen, - resources.getString(R.string.days)); - } - void updateCardHeaders(View view) { updateColor(view, R.id.tvHistory); - updateColor(view, R.id.tvOverview); updateColor(view, R.id.tvStrength); updateColor(view, R.id.tvStreaks); updateColor(view, R.id.tvWeekdayFreq); @@ -90,76 +61,4 @@ public class ShowHabitHelper InterfaceUtils.getStyledColor(fragment.getContext(), R.attr.mediumContrastTextColor); } - - void updateMainHeader(View view) - { - if (fragment.habit == null) return; - - TextView questionLabel = - (TextView) view.findViewById(R.id.questionLabel); - questionLabel.setTextColor(fragment.activeColor); - questionLabel.setText(fragment.habit.getDescription()); - - TextView reminderLabel = - (TextView) view.findViewById(R.id.reminderLabel); - - if (fragment.habit.hasReminder()) - { - Reminder reminder = fragment.habit.getReminder(); - reminderLabel.setText( - DateUtils.formatTime(fragment.getActivity(), reminder.getHour(), - reminder.getMinute())); - } - else - { - reminderLabel.setText( - fragment.getResources().getString(R.string.reminder_off)); - } - - TextView frequencyLabel = - (TextView) view.findViewById(R.id.frequencyLabel); - frequencyLabel.setText(getFreqText()); - - if (fragment.habit.getDescription().isEmpty()) - questionLabel.setVisibility(View.GONE); - } - - void updateScore(View view) - { - if (fragment.habit == null) return; - if (view == null) return; - - float todayPercentage = fragment.todayScore / Score.MAX_VALUE; - float monthDiff = - todayPercentage - (fragment.lastMonthScore / Score.MAX_VALUE); - float yearDiff = - todayPercentage - (fragment.lastYearScore / Score.MAX_VALUE); - - RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); - int androidColor = ColorUtils.getColor(fragment.getActivity(), - fragment.habit.getColor()); - scoreRing.setColor(androidColor); - scoreRing.setPercentage(todayPercentage); - - TextView scoreLabel = (TextView) view.findViewById(R.id.scoreLabel); - TextView monthDiffLabel = - (TextView) view.findViewById(R.id.monthDiffLabel); - TextView yearDiffLabel = - (TextView) view.findViewById(R.id.yearDiffLabel); - - scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); - - String minus = "\u2212"; - monthDiffLabel.setText( - String.format("%s%.0f%%", (monthDiff >= 0 ? "+" : minus), - Math.abs(monthDiff) * 100)); - yearDiffLabel.setText( - String.format("%s%.0f%%", (yearDiff >= 0 ? "+" : minus), - Math.abs(yearDiff) * 100)); - - monthDiffLabel.setTextColor( - monthDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); - yearDiffLabel.setTextColor( - yearDiff >= 0 ? fragment.activeColor : fragment.inactiveColor); - } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java index d9163f0e5..223703393 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java @@ -26,6 +26,4 @@ public interface HabitDataView void setHabit(Habit habit); void refreshData(); - - void postInvalidate(); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCardView.java new file mode 100644 index 000000000..ae952597b --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCardView.java @@ -0,0 +1,186 @@ +/* + * 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.ui.habits.show.views; + +import android.content.*; +import android.support.annotation.*; +import android.util.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.utils.*; + +import butterknife.*; + +public class OverviewCardView extends LinearLayout + implements ModelObservable.Listener +{ + @Nullable + private Habit habit; + + @NonNull + private Cache cache; + + @BindView(R.id.scoreRing) + RingView scoreRing; + + @BindView(R.id.scoreLabel) + TextView scoreLabel; + + @BindView(R.id.monthDiffLabel) + TextView monthDiffLabel; + + @BindView(R.id.yearDiffLabel) + TextView yearDiffLabel; + + @BindView(R.id.title) + TextView title; + + private int color; + + public OverviewCardView(Context context) + { + super(context); + init(); + } + + public OverviewCardView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + @Override + public void onModelChange() + { + refreshCache(); + } + + public void setHabit(@Nullable Habit habit) + { + this.habit = habit; + color = ColorUtils.getColor(getContext(), habit.getColor()); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + refreshCache(); + if(habit != null) habit.getObservable().addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + if(habit != null) habit.getObservable().removeListener(this); + super.onDetachedFromWindow(); + } + + private void init() + { + inflate(getContext(), R.layout.show_habit_overview, this); + ButterKnife.bind(this); + cache = new Cache(); + + if(isInEditMode()) initEditMode(); + } + + private void initEditMode() + { + color = ColorUtils.getAndroidTestColor(1); + cache.todayScore = Score.MAX_VALUE * 0.6f; + cache.lastMonthScore = Score.MAX_VALUE * 0.42f; + cache.lastYearScore = Score.MAX_VALUE * 0.75f; + updateScore(); + } + + private void refreshCache() + { + new BaseTask() + { + @Override + protected void doInBackground() + { + if(habit == null) return; + ScoreList scores = habit.getScores(); + + + long today = DateUtils.getStartOfToday(); + long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; + long lastYear = today - 365 * DateUtils.millisecondsInOneDay; + + cache.todayScore = (float) scores.getTodayValue(); + cache.lastMonthScore = (float) scores.getValue(lastMonth); + cache.lastYearScore = (float) scores.getValue(lastYear); + } + + @Override + protected void onPostExecute(Void aVoid) + { + updateScore(); + super.onPostExecute(aVoid); + } + }.execute(); + } + + void updateScore() + { + float todayPercentage = cache.todayScore / Score.MAX_VALUE; + float monthDiff = todayPercentage - (cache.lastMonthScore / Score.MAX_VALUE); + float yearDiff = todayPercentage - (cache.lastYearScore / Score.MAX_VALUE); + + scoreRing.setColor(color); + scoreRing.setPercentage(todayPercentage); + + scoreLabel.setTextColor(color); + scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); + + title.setTextColor(color); + + monthDiffLabel.setText(formatPercentageDiff(monthDiff)); + yearDiffLabel.setText(formatPercentageDiff(yearDiff)); + + int inactiveColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + + monthDiffLabel.setTextColor(monthDiff >= 0 ? color : inactiveColor); + yearDiffLabel.setTextColor(yearDiff >= 0 ? color : inactiveColor); + + postInvalidate(); + } + + private String formatPercentageDiff(float percentageDiff) + { + return String.format("%s%.0f%%", (percentageDiff >= 0 ? "+" : "\u2212"), + Math.abs(percentageDiff) * 100); + } + + private class Cache + { + public float todayScore; + + public float lastMonthScore; + + public float lastYearScore; + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCardView.java new file mode 100644 index 000000000..dad8fac72 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCardView.java @@ -0,0 +1,153 @@ +/* + * 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.ui.habits.show.views; + +import android.annotation.*; +import android.content.*; +import android.content.res.*; +import android.support.annotation.*; +import android.util.*; +import android.view.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; + +import butterknife.*; + +public class SubtitleCardView extends LinearLayout + implements ModelObservable.Listener +{ + @BindView(R.id.questionLabel) + TextView questionLabel; + + @BindView(R.id.frequencyLabel) + TextView frequencyLabel; + + @BindView(R.id.reminderLabel) + TextView reminderLabel; + + @Nullable + private Habit habit; + + public SubtitleCardView(Context context) + { + super(context); + init(); + } + + public SubtitleCardView(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + @Override + public void onModelChange() + { + refreshData(); + } + + public void setHabit(@Nullable Habit habit) + { + this.habit = habit; + refreshData(); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + if (habit != null) habit.getObservable().addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + if (habit != null) habit.getObservable().removeListener(this); + super.onDetachedFromWindow(); + } + + private void init() + { + Context context = getContext(); + inflate(context, R.layout.show_habit_subtitle, this); + ButterKnife.bind(this); + + if (isInEditMode()) initEditMode(); + } + + @SuppressLint("SetTextI18n") + private void initEditMode() + { + questionLabel.setTextColor(ColorUtils.getAndroidTestColor(1)); + questionLabel.setText("Have you meditated today?"); + reminderLabel.setText("08:00"); + } + + private void refreshData() + { + if (habit == null) return; + int color = ColorUtils.getColor(getContext(), habit.getColor()); + + reminderLabel.setText(getResources().getString(R.string.reminder_off)); + questionLabel.setVisibility(VISIBLE); + + questionLabel.setTextColor(color); + questionLabel.setText(habit.getDescription()); + frequencyLabel.setText(toText(habit.getFrequency())); + + if (habit.hasReminder()) updateReminderText(habit.getReminder()); + + if (habit.getDescription().isEmpty()) + questionLabel.setVisibility(View.GONE); + + postInvalidate(); + } + + String toText(Frequency freq) + { + Resources resources = getResources(); + Integer num = freq.getNumerator(); + Integer den = freq.getDenominator(); + + if (num.equals(den)) return resources.getString(R.string.every_day); + + if (num == 1) + { + if (den == 7) return resources.getString(R.string.every_week); + if (den % 7 == 0) + return resources.getString(R.string.every_x_weeks, den / 7); + return resources.getString(R.string.every_x_days, den); + } + + String times_every = resources.getString(R.string.times_every); + return String.format("%d %s %d %s", num, times_every, den, + resources.getString(R.string.days)); + } + + private void updateReminderText(Reminder reminder) + { + reminderLabel.setText( + DateUtils.formatTime(getContext(), reminder.getHour(), + reminder.getMinute())); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java b/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java index e8116c1b0..9499bccbf 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/ColorUtils.java @@ -19,11 +19,11 @@ package org.isoron.uhabits.utils; -import android.content.Context; -import android.graphics.Color; -import android.util.Log; +import android.content.*; +import android.graphics.*; +import android.util.*; -import org.isoron.uhabits.R; +import org.isoron.uhabits.*; public abstract class ColorUtils { diff --git a/app/src/main/res/layout/list_habits_preview.xml b/app/src/main/res/layout/list_habits_preview.xml index ff52cd785..87250848d 100644 --- a/app/src/main/res/layout/list_habits_preview.xml +++ b/app/src/main/res/layout/list_habits_preview.xml @@ -60,13 +60,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> - - - + - - - + diff --git a/app/src/main/res/layout/show_habit.xml b/app/src/main/res/layout/show_habit.xml index 045f6b5fa..9bfd2c569 100644 --- a/app/src/main/res/layout/show_habit.xml +++ b/app/src/main/res/layout/show_habit.xml @@ -20,7 +20,6 @@ + android:clipToPadding="false"> - - - - - - - + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + style="@style/Card" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/show_habit_preview.xml b/app/src/main/res/layout/show_habit_preview.xml new file mode 100644 index 000000000..4ff01a1a9 --- /dev/null +++ b/app/src/main/res/layout/show_habit_preview.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/show_habit_subtitle.xml b/app/src/main/res/layout/show_habit_subtitle.xml new file mode 100644 index 000000000..4c57473c0 --- /dev/null +++ b/app/src/main/res/layout/show_habit_subtitle.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_show_habit.xml b/app/src/main/res/values/styles_show_habit.xml new file mode 100644 index 000000000..e0434f2b3 --- /dev/null +++ b/app/src/main/res/values/styles_show_habit.xml @@ -0,0 +1,37 @@ + + + + + + \ No newline at end of file From a11ad6e90959d2fdabb1ee1b8802e90ec1c6f685 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Wed, 22 Jun 2016 19:47:16 -0400 Subject: [PATCH 034/184] ShowHabit: Refactor score chart --- ...yViewTest.java => FrequencyChartTest.java} | 6 +- .../show/views/HabitHistoryViewTest.java | 4 +- ...ScoreViewTest.java => ScoreChartTest.java} | 16 +- ...reakViewTest.java => StreakChartTest.java} | 6 +- .../models/memory/MemoryScoreList.java | 1 + .../models/sqlite/SQLiteScoreList.java | 2 + .../ui/habits/edit/HistoryEditorDialog.java | 6 +- .../ui/habits/show/ShowHabitFragment.java | 86 ++------ .../ui/habits/show/ShowHabitHelper.java | 1 - ...FrequencyView.java => FrequencyChart.java} | 6 +- ...HabitHistoryView.java => HistoryView.java} | 12 +- ...verviewCardView.java => OverviewCard.java} | 6 +- .../ui/habits/show/views/ScoreCard.java | 198 ++++++++++++++++++ .../{HabitScoreView.java => ScoreChart.java} | 140 +++---------- ...ableDataView.java => ScrollableChart.java} | 6 +- ...{HabitStreakView.java => StreakChart.java} | 6 +- ...ubtitleCardView.java => SubtitleCard.java} | 6 +- .../isoron/uhabits/utils/InterfaceUtils.java | 4 +- .../widgets/FrequencyWidgetProvider.java | 4 +- .../widgets/HistoryWidgetProvider.java | 4 +- .../uhabits/widgets/ScoreWidgetProvider.java | 32 +-- .../uhabits/widgets/StreakWidgetProvider.java | 4 +- app/src/main/res/layout/show_habit.xml | 48 +---- .../main/res/layout/show_habit_preview.xml | 17 +- .../main/res/layout/show_habit_strength.xml | 48 +++++ 25 files changed, 378 insertions(+), 291 deletions(-) rename app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/{HabitFrequencyViewTest.java => FrequencyChartTest.java} (93%) rename app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/{HabitScoreViewTest.java => ScoreChartTest.java} (85%) rename app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/{HabitStreakViewTest.java => StreakChartTest.java} (93%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{HabitFrequencyView.java => FrequencyChart.java} (98%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{HabitHistoryView.java => HistoryView.java} (96%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{OverviewCardView.java => OverviewCard.java} (96%) create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{HabitScoreView.java => ScoreChart.java} (78%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{ScrollableDataView.java => ScrollableChart.java} (95%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{HabitStreakView.java => StreakChart.java} (98%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{SubtitleCardView.java => SubtitleCard.java} (96%) create mode 100644 app/src/main/res/layout/show_habit_strength.xml diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChartTest.java similarity index 93% rename from app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChartTest.java index a315ee3fb..a1ebb663d 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChartTest.java @@ -30,9 +30,9 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitFrequencyViewTest extends BaseViewTest +public class FrequencyChartTest extends BaseViewTest { - private HabitFrequencyView view; + private FrequencyChart view; @Before public void setUp() @@ -42,7 +42,7 @@ public class HabitFrequencyViewTest extends BaseViewTest fixtures.purgeHabits(habitList); Habit habit = fixtures.createLongHabit(); - view = new HabitFrequencyView(targetContext); + view = new FrequencyChart(targetContext); view.setHabit(habit); refreshData(view); measureView(dpToPixels(300), dpToPixels(100), view); diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java index 5066471e8..3e180583c 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java @@ -40,7 +40,7 @@ public class HabitHistoryViewTest extends BaseViewTest { private Habit habit; - private HabitHistoryView view; + private HistoryView view; @Before public void setUp() @@ -50,7 +50,7 @@ public class HabitHistoryViewTest extends BaseViewTest fixtures.purgeHabits(habitList); habit = fixtures.createLongHabit(); - view = new HabitHistoryView(targetContext); + view = new HistoryView(targetContext); view.setHabit(habit); measureView(dpToPixels(400), dpToPixels(200), view); refreshData(view); diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/ScoreChartTest.java similarity index 85% rename from app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/ScoreChartTest.java index 114087f28..03f586fa5 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/ScoreChartTest.java @@ -25,18 +25,20 @@ import android.util.Log; import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.utils.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitScoreViewTest extends BaseViewTest +public class ScoreChartTest extends BaseViewTest { private Habit habit; - private HabitScoreView view; + private ScoreChart view; + @Override @Before public void setUp() { @@ -45,10 +47,10 @@ public class HabitScoreViewTest extends BaseViewTest fixtures.purgeHabits(habitList); habit = fixtures.createLongHabit(); - view = new HabitScoreView(targetContext); - view.setHabit(habit); + view = new ScoreChart(targetContext); + view.setScores(habit.getScores().getAll()); + view.setPrimaryColor(ColorUtils.getColor(targetContext, habit.getColor())); view.setBucketSize(7); - refreshData(view); measureView(dpToPixels(300), dpToPixels(200), view); } @@ -79,8 +81,8 @@ public class HabitScoreViewTest extends BaseViewTest @Test public void testRender_withMonthlyBucket() throws Throwable { + view.setScores(habit.getScores().groupBy(DateUtils.TruncateField.MONTH)); view.setBucketSize(30); - view.refreshData(); view.invalidate(); assertRenders(view, "HabitScoreView/renderMonthly.png"); @@ -96,8 +98,8 @@ public class HabitScoreViewTest extends BaseViewTest @Test public void testRender_withYearlyBucket() throws Throwable { + view.setScores(habit.getScores().groupBy(DateUtils.TruncateField.YEAR)); view.setBucketSize(365); - view.refreshData(); view.invalidate(); assertRenders(view, "HabitScoreView/renderYearly.png"); diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/StreakChartTest.java similarity index 93% rename from app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/StreakChartTest.java index aad240ac9..a9fef9304 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/StreakChartTest.java @@ -30,9 +30,9 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitStreakViewTest extends BaseViewTest +public class StreakChartTest extends BaseViewTest { - private HabitStreakView view; + private StreakChart view; @Override @Before @@ -43,7 +43,7 @@ public class HabitStreakViewTest extends BaseViewTest fixtures.purgeHabits(habitList); Habit habit = fixtures.createLongHabit(); - view = new HabitStreakView(targetContext); + view = new StreakChart(targetContext); measureView(dpToPixels(300), dpToPixels(100), view); view.setHabit(habit); diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java index 358415473..2705d407d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryScoreList.java @@ -44,6 +44,7 @@ public class MemoryScoreList extends ScoreList if (s.getTimestamp() >= timestamp) discard.add(s); list.removeAll(discard); + getObservable().notifyListeners(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java index e20986134..ecafd2cec 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteScoreList.java @@ -134,6 +134,8 @@ public class SQLiteScoreList extends ScoreList .where("habit = ?", habit.getId()) .and("timestamp >= ?", timestamp) .execute(); + + getObservable().notifyListeners(); } @Nullable diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java index bdf08d88d..183d3d0ed 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java @@ -32,7 +32,7 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView; +import org.isoron.uhabits.ui.habits.show.views.HistoryView; import javax.inject.Inject; @@ -43,7 +43,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment private Listener listener; - HabitHistoryView historyView; + HistoryView historyView; @Inject HabitList habitList; @@ -53,7 +53,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment { Context context = getActivity(); HabitsApplication.getComponent().inject(this); - historyView = new HabitHistoryView(context, null); + historyView = new HistoryView(context, null); if (savedInstanceState != null) { diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 9352e58d3..11be5848f 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -22,13 +22,11 @@ package org.isoron.uhabits.ui.habits.show; import android.os.*; import android.support.v4.app.*; import android.view.*; -import android.widget.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.ui.habits.edit.*; import org.isoron.uhabits.ui.habits.show.views.*; -import org.isoron.uhabits.utils.*; import java.util.*; @@ -39,44 +37,33 @@ public class ShowHabitFragment extends Fragment { Habit habit; - float todayScore; - - float lastMonthScore; - - float lastYearScore; - int activeColor; int inactiveColor; - int previousScoreInterval; - private ShowHabitHelper helper; protected ShowHabitActivity activity; private List dataViews; - @BindView(R.id.sStrengthInterval) - Spinner sStrengthInterval; - - @BindView(R.id.scoreView) - HabitScoreView habitScoreView; - @BindView(R.id.historyView) - HabitHistoryView habitHistoryView; + HistoryView historyView; @BindView(R.id.punchcardView) - HabitFrequencyView habitFrequencyView; + FrequencyChart frequencyChart; + + @BindView(R.id.streakChart) + StreakChart streakChart; - @BindView(R.id.streakView) - HabitStreakView habitStreakView; + @BindView(R.id.subtitleCard) + SubtitleCard subtitleCard; - @BindView(R.id.subtitle) - SubtitleCardView subtitleView; + @BindView(R.id.overviewCard) + OverviewCard overviewCard; - @BindView(R.id.overview) - OverviewCardView overview; + @BindView(R.id.strengthCard) + ScoreCard scoreCard; @Override public View onCreateView(LayoutInflater inflater, @@ -92,15 +79,6 @@ public class ShowHabitFragment extends Fragment habit = activity.getHabit(); helper.updateColors(); - int defaultScoreInterval = - InterfaceUtils.getDefaultScoreInterval(getContext()); - previousScoreInterval = defaultScoreInterval; - setScoreBucketSize(defaultScoreInterval); - - sStrengthInterval.setSelection(defaultScoreInterval); - sStrengthInterval.setOnItemSelectedListener( - new OnItemSelectedListener()); - createDataViews(); helper.updateCardHeaders(view); setHasOptionsMenu(true); @@ -118,14 +96,14 @@ public class ShowHabitFragment extends Fragment private void createDataViews() { - subtitleView.setHabit(habit); - overview.setHabit(habit); + subtitleCard.setHabit(habit); + overviewCard.setHabit(habit); + scoreCard.setHabit(habit); dataViews = new LinkedList<>(); - dataViews.add(habitScoreView); - dataViews.add(habitHistoryView); - dataViews.add(habitFrequencyView); - dataViews.add(habitStreakView); + dataViews.add(historyView); + dataViews.add(frequencyChart); + dataViews.add(streakChart); for (HabitDataView dataView : dataViews) dataView.setHabit(habit); @@ -156,17 +134,6 @@ public class ShowHabitFragment extends Fragment return true; } - private void setScoreBucketSize(int position) - { - if (getView() == null) return; - - habitScoreView.setBucketSize( - HabitScoreView.DEFAULT_BUCKET_SIZES[position]); - - InterfaceUtils.setDefaultScoreInterval(getContext(), position); - previousScoreInterval = position; - } - @Override public void onModelChange() { @@ -190,23 +157,4 @@ public class ShowHabitFragment extends Fragment habit.getObservable().removeListener(this); super.onPause(); } - - private class OnItemSelectedListener - implements AdapterView.OnItemSelectedListener - { - @Override - public void onItemSelected(AdapterView parent, - View view, - int position, - long id) - { - setScoreBucketSize(position); - } - - @Override - public void onNothingSelected(AdapterView parent) - { - - } - } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java index 168355e21..38f2062e4 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java @@ -37,7 +37,6 @@ public class ShowHabitHelper void updateCardHeaders(View view) { updateColor(view, R.id.tvHistory); - updateColor(view, R.id.tvStrength); updateColor(view, R.id.tvStreaks); updateColor(view, R.id.tvWeekdayFreq); updateColor(view, R.id.scoreLabel); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChart.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChart.java index f24317524..df069c455 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitFrequencyView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChart.java @@ -31,7 +31,7 @@ import org.isoron.uhabits.utils.*; import java.text.*; import java.util.*; -public class HabitFrequencyView extends ScrollableDataView +public class FrequencyChart extends ScrollableChart implements HabitDataView, ModelObservable.Listener { private Paint pGrid; @@ -70,13 +70,13 @@ public class HabitFrequencyView extends ScrollableDataView private HashMap frequency; - public HabitFrequencyView(Context context) + public FrequencyChart(Context context) { super(context); init(); } - public HabitFrequencyView(Context context, AttributeSet attrs) + public FrequencyChart(Context context, AttributeSet attrs) { super(context, attrs); this.primaryColor = ColorUtils.getColor(getContext(), 7); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HistoryView.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HistoryView.java index 7f7c9a504..0918bbaf6 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HistoryView.java @@ -33,10 +33,9 @@ import org.isoron.uhabits.utils.*; import java.text.*; import java.util.*; -public class HabitHistoryView extends ScrollableDataView implements - HabitDataView, - ToggleRepetitionTask.Listener, - ModelObservable.Listener +public class HistoryView extends ScrollableChart implements HabitDataView, + ToggleRepetitionTask.Listener, + ModelObservable.Listener { private Habit habit; @@ -89,13 +88,13 @@ public class HabitHistoryView extends ScrollableDataView implements private float headerOverflow = 0; - public HabitHistoryView(Context context) + public HistoryView(Context context) { super(context); init(); } - public HabitHistoryView(Context context, AttributeSet attrs) + public HistoryView(Context context, AttributeSet attrs) { super(context, attrs); init(); @@ -169,6 +168,7 @@ public class HabitHistoryView extends ScrollableDataView implements postInvalidate(); } + @Override public void setHabit(Habit habit) { this.habit = habit; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCard.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCardView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCard.java index ae952597b..845b0adec 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCardView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCard.java @@ -31,7 +31,7 @@ import org.isoron.uhabits.utils.*; import butterknife.*; -public class OverviewCardView extends LinearLayout +public class OverviewCard extends LinearLayout implements ModelObservable.Listener { @Nullable @@ -57,13 +57,13 @@ public class OverviewCardView extends LinearLayout private int color; - public OverviewCardView(Context context) + public OverviewCard(Context context) { super(context); init(); } - public OverviewCardView(Context context, AttributeSet attrs) + public OverviewCard(Context context, AttributeSet attrs) { super(context, attrs); init(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java new file mode 100644 index 000000000..3874aaa22 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java @@ -0,0 +1,198 @@ +/* + * 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.ui.habits.show.views; + +import android.content.*; +import android.support.annotation.*; +import android.util.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.utils.*; + +import java.util.*; + +import butterknife.*; + +public class ScoreCard extends RelativeLayout + implements ModelObservable.Listener +{ + public static final int[] BUCKET_SIZES = { 1, 7, 31, 92, 365 }; + + @BindView(R.id.spinner) + Spinner spinner; + + @BindView(R.id.scoreView) + ScoreChart chart; + + @BindView(R.id.title) + TextView title; + + @Nullable + private Habit habit; + + private int color; + + private int bucketSize; + + public ScoreCard(Context context) + { + super(context); + init(); + } + + public ScoreCard(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + @OnItemSelected(R.id.spinner) + public void onItemSelected(int position) + { + setBucketSizeFromPosition(position); + } + + @Override + public void onModelChange() + { + refreshData(); + } + + public void setHabit(@NonNull Habit habit) + { + this.habit = habit; + color = ColorUtils.getColor(getContext(), habit.getColor()); + refreshData(); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + if (habit != null) + { + habit.getObservable().addListener(this); + habit.getScores().getObservable().addListener(this); + } + } + + @Override + protected void onDetachedFromWindow() + { + if (habit != null) + { + habit.getObservable().removeListener(this); + habit.getScores().getObservable().removeListener(this); + } + + super.onDetachedFromWindow(); + } + + @NonNull + private DateUtils.TruncateField getTruncateField() + { + DateUtils.TruncateField field; + + switch (bucketSize) + { + case 7: + field = DateUtils.TruncateField.WEEK_NUMBER; + break; + + case 365: + field = DateUtils.TruncateField.YEAR; + break; + + case 92: + field = DateUtils.TruncateField.QUARTER; + break; + + default: + Log.e("ScoreCard", + String.format("Unknown bucket size: %d", bucketSize)); + // continue to case 31 + + case 31: + field = DateUtils.TruncateField.MONTH; + break; + } + + return field; + } + + private void init() + { + inflate(getContext(), R.layout.show_habit_strength, this); + ButterKnife.bind(this); + + int defaultPosition = getDefaultSpinnerPosition(); + setBucketSizeFromPosition(defaultPosition); + spinner.setSelection(defaultPosition); + + if(isInEditMode()) + { + spinner.setVisibility(GONE); + title.setTextColor(ColorUtils.getAndroidTestColor(1)); + chart.setPrimaryColor(ColorUtils.getAndroidTestColor(1)); + chart.populateWithRandomData(); + } + } + + private int getDefaultSpinnerPosition() + { + if(isInEditMode()) return 0; + return InterfaceUtils.getDefaultScoreSpinnerPosition(getContext()); + } + + private void refreshData() + { + if (habit == null) return; + + title.setTextColor(color); + chart.setPrimaryColor(color); + + new BaseTask() + { + @Override + protected void doInBackground() + { + List scores; + + if (bucketSize == 1) scores = habit.getScores().getAll(); + else scores = habit.getScores().groupBy(getTruncateField()); + + chart.setScores(scores); + chart.setBucketSize(bucketSize); + } + }.execute(); + } + + private void setBucketSizeFromPosition(int position) + { + if(isInEditMode()) return; + + InterfaceUtils.setDefaultScoreSpinnerPosition(getContext(), position); + bucketSize = BUCKET_SIZES[position]; + refreshData(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreChart.java similarity index 78% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreChart.java index b162b9786..b641dd225 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreChart.java @@ -26,14 +26,12 @@ import android.util.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.utils.*; import java.text.*; import java.util.*; -public class HabitScoreView extends ScrollableDataView - implements HabitDataView, ModelObservable.Listener +public class ScoreChart extends ScrollableChart { private static final PorterDuffXfermode XFERMODE_CLEAR = new PorterDuffXfermode(PorterDuff.Mode.CLEAR); @@ -41,14 +39,10 @@ public class HabitScoreView extends ScrollableDataView private static final PorterDuffXfermode XFERMODE_SRC = new PorterDuffXfermode(PorterDuff.Mode.SRC); - public static int DEFAULT_BUCKET_SIZES[] = {1, 7, 31, 92, 365}; - private Paint pGrid; private float em; - private Habit habit; - private SimpleDateFormat dfMonth; private SimpleDateFormat dfDay; @@ -80,8 +74,6 @@ public class HabitScoreView extends ScrollableDataView private int bucketSize = 7; - private int footerHeight; - private int backgroundColor; private Bitmap drawingCache; @@ -96,58 +88,42 @@ public class HabitScoreView extends ScrollableDataView private String previousMonthText; - public HabitScoreView(Context context) + public ScoreChart(Context context) { super(context); init(); } - public HabitScoreView(Context context, AttributeSet attrs) + public ScoreChart(Context context, AttributeSet attrs) { super(context, attrs); - this.primaryColor = ColorUtils.getColor(getContext(), 7); init(); } - @Override - public void onModelChange() - { - refreshData(); - } - - @Override - public void refreshData() + @Deprecated + public void setBucketSize(int bucketSize) { - if (isInEditMode()) generateRandomData(); - else - { - if (habit == null) return; - if (bucketSize == 1) scores = habit.getScores().getAll(); - else scores = habit.getScores().groupBy(getTruncateField()); - - createColors(); - } - + this.bucketSize = bucketSize; postInvalidate(); } - public void setBucketSize(int bucketSize) + public void setIsTransparencyEnabled(boolean enabled) { - this.bucketSize = bucketSize; + this.isTransparencyEnabled = enabled; + createColors(); + requestLayout(); } - @Override - public void setHabit(Habit habit) + public void setPrimaryColor(int primaryColor) { - this.habit = habit; - createColors(); + this.primaryColor = primaryColor; + postInvalidate(); } - public void setIsTransparencyEnabled(boolean enabled) + public void setScores(@NonNull List scores) { - this.isTransparencyEnabled = enabled; - createColors(); - requestLayout(); + this.scores = scores; + postInvalidate(); } protected void createPaints() @@ -163,30 +139,6 @@ public class HabitScoreView extends ScrollableDataView pGrid.setAntiAlias(true); } - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - new BaseTask() - { - @Override - protected void doInBackground() - { - refreshData(); - } - }.execute(); - habit.getObservable().addListener(this); - habit.getScores().getObservable().addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - habit.getScores().getObservable().removeListener(this); - habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); - } - @Override protected void onDraw(Canvas canvas) { @@ -205,7 +157,7 @@ public class HabitScoreView extends ScrollableDataView activeCanvas = canvas; } - if (habit == null || scores == null) return; + if (scores == null) return; rect.set(0, 0, nColumns * columnWidth, columnHeight); rect.offset(0, paddingTop); @@ -220,16 +172,13 @@ public class HabitScoreView extends ScrollableDataView previousYearText = ""; skipYear = 0; - long currentDate = DateUtils.getStartOfToday(); - - for (int k = 0; k < nColumns + getDataOffset() - 1; k++) - currentDate -= bucketSize * DateUtils.millisecondsInOneDay; - for (int k = 0; k < nColumns; k++) { - int score = 0; int offset = nColumns - k - 1 + getDataOffset(); - if (offset < scores.size()) score = scores.get(offset).getValue(); + if (offset >= scores.size()) continue; + + int score = scores.get(offset).getValue(); + long timestamp = scores.get(offset).getTimestamp(); double relativeScore = ((double) score) / Score.MAX_VALUE; int height = (int) (columnHeight * relativeScore); @@ -250,9 +199,7 @@ public class HabitScoreView extends ScrollableDataView rect.set(0, 0, columnWidth, columnHeight); rect.offset(k * columnWidth, paddingTop); - drawFooter(activeCanvas, rect, currentDate); - - currentDate += bucketSize * DateUtils.millisecondsInOneDay; + drawFooter(activeCanvas, rect, timestamp); } if (activeCanvas != canvas) canvas.drawBitmap(drawingCache, 0, 0, null); @@ -279,18 +226,17 @@ public class HabitScoreView extends ScrollableDataView pText.setTextSize(Math.min(textSize, maxTextSize)); em = pText.getFontSpacing(); - footerHeight = (int) (3 * em); + int footerHeight = (int) (3 * em); paddingTop = (int) (em); baseSize = (height - footerHeight - paddingTop) / 8; - setScrollerBucketSize(baseSize); - columnWidth = baseSize; columnWidth = Math.max(columnWidth, getMaxDayWidth() * 1.5f); columnWidth = Math.max(columnWidth, getMaxMonthWidth() * 1.2f); nColumns = (int) (width / columnWidth); columnWidth = (float) width / nColumns; + setScrollerBucketSize((int) columnWidth); columnHeight = 8 * baseSize; @@ -304,9 +250,7 @@ public class HabitScoreView extends ScrollableDataView private void createColors() { - if (habit != null) this.primaryColor = - ColorUtils.getColor(getContext(), habit.getColor()); - + primaryColor = Color.BLACK; textColor = InterfaceUtils.getStyledColor(getContext(), R.attr.mediumContrastTextColor); gridColor = InterfaceUtils.getStyledColor(getContext(), @@ -411,7 +355,7 @@ public class HabitScoreView extends ScrollableDataView if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC); } - private void generateRandomData() + public void populateWithRandomData() { Random random = new Random(); scores = new LinkedList<>(); @@ -461,38 +405,6 @@ public class HabitScoreView extends ScrollableDataView return maxMonthWidth; } - @NonNull - private DateUtils.TruncateField getTruncateField() - { - DateUtils.TruncateField field; - - switch (bucketSize) - { - case 7: - field = DateUtils.TruncateField.WEEK_NUMBER; - break; - - case 365: - field = DateUtils.TruncateField.YEAR; - break; - - case 92: - field = DateUtils.TruncateField.QUARTER; - break; - - default: - Log.e("HabitScoreView", - String.format("Unknown bucket size: %d", bucketSize)); - // continue to case 31 - - case 31: - field = DateUtils.TruncateField.MONTH; - break; - } - - return field; - } - private void init() { createPaints(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableChart.java similarity index 95% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableChart.java index 09fa9dd97..9e4cdea43 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableDataView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableChart.java @@ -25,7 +25,7 @@ import android.util.*; import android.view.*; import android.widget.*; -public abstract class ScrollableDataView extends View +public abstract class ScrollableChart extends View implements GestureDetector.OnGestureListener, ValueAnimator.AnimatorUpdateListener { @@ -40,13 +40,13 @@ public abstract class ScrollableDataView extends View private ValueAnimator scrollAnimator; - public ScrollableDataView(Context context) + public ScrollableChart(Context context) { super(context); init(context); } - public ScrollableDataView(Context context, AttributeSet attrs) + public ScrollableChart(Context context, AttributeSet attrs) { super(context, attrs); init(context); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/StreakChart.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/StreakChart.java index 68ebdf934..bca5499a7 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/StreakChart.java @@ -32,7 +32,7 @@ import org.isoron.uhabits.utils.*; import java.text.*; import java.util.*; -public class HabitStreakView extends View +public class StreakChart extends View implements HabitDataView, ModelObservable.Listener { private Habit habit; @@ -73,13 +73,13 @@ public class HabitStreakView extends View private int reverseTextColor; - public HabitStreakView(Context context) + public StreakChart(Context context) { super(context); init(); } - public HabitStreakView(Context context, AttributeSet attrs) + public StreakChart(Context context, AttributeSet attrs) { super(context, attrs); this.primaryColor = ColorUtils.getColor(getContext(), 7); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCardView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCard.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCardView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCard.java index dad8fac72..dbc18f530 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCardView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCard.java @@ -33,7 +33,7 @@ import org.isoron.uhabits.utils.*; import butterknife.*; -public class SubtitleCardView extends LinearLayout +public class SubtitleCard extends LinearLayout implements ModelObservable.Listener { @BindView(R.id.questionLabel) @@ -48,13 +48,13 @@ public class SubtitleCardView extends LinearLayout @Nullable private Habit habit; - public SubtitleCardView(Context context) + public SubtitleCard(Context context) { super(context); init(); } - public SubtitleCardView(Context context, AttributeSet attrs) + public SubtitleCard(Context context, AttributeSet attrs) { super(context, attrs); init(); diff --git a/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java index cde5c77d7..9d5916d2d 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java @@ -269,13 +269,13 @@ public abstract class InterfaceUtils } - public static void setDefaultScoreInterval(Context context, int position) + public static void setDefaultScoreSpinnerPosition(Context context, int position) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.edit().putInt("pref_score_view_interval", position).apply(); } - public static int getDefaultScoreInterval(Context context) + public static int getDefaultScoreSpinnerPosition(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1); diff --git a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java index e186d3224..0b7b12610 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java @@ -28,14 +28,14 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.widgets.views.GraphWidgetView; import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.HabitFrequencyView; +import org.isoron.uhabits.ui.habits.show.views.FrequencyChart; public class FrequencyWidgetProvider extends BaseWidgetProvider { @Override protected View buildCustomView(Context context, Habit habit) { - HabitFrequencyView dataView = new HabitFrequencyView(context); + FrequencyChart dataView = new FrequencyChart(context); GraphWidgetView view = new GraphWidgetView(context, dataView); view.setHabit(habit); return view; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java index b9d98f2e0..5ad306198 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java @@ -27,14 +27,14 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.widgets.views.GraphWidgetView; import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.HabitHistoryView; +import org.isoron.uhabits.ui.habits.show.views.HistoryView; public class HistoryWidgetProvider extends BaseWidgetProvider { @Override protected View buildCustomView(Context context, Habit habit) { - HabitHistoryView dataView = new HabitHistoryView(context); + HistoryView dataView = new HistoryView(context); GraphWidgetView view = new GraphWidgetView(context, dataView); view.setHabit(habit); return view; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java index ee998de8d..63adea38e 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java @@ -18,33 +18,33 @@ */ package org.isoron.uhabits.widgets; -import android.app.PendingIntent; -import android.content.Context; -import android.view.View; +import android.app.*; +import android.content.*; +import android.view.*; -import org.isoron.uhabits.HabitBroadcastReceiver; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.utils.InterfaceUtils; -import org.isoron.uhabits.widgets.views.GraphWidgetView; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.HabitScoreView; +import org.apache.commons.lang3.*; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.utils.*; public class ScoreWidgetProvider extends BaseWidgetProvider { @Override protected View buildCustomView(Context context, Habit habit) { - int defaultScoreInterval = InterfaceUtils.getDefaultScoreInterval(context); - int size = HabitScoreView.DEFAULT_BUCKET_SIZES[defaultScoreInterval]; + int defaultScoreInterval = InterfaceUtils.getDefaultScoreSpinnerPosition(context); + int size = ScoreCard.BUCKET_SIZES[defaultScoreInterval]; - HabitScoreView dataView = new HabitScoreView(context); + ScoreChart dataView = new ScoreChart(context); dataView.setIsTransparencyEnabled(true); dataView.setBucketSize(size); - GraphWidgetView view = new GraphWidgetView(context, dataView); - view.setHabit(habit); - return view; +// GraphWidgetView view = new GraphWidgetView(context, dataView); +// view.setHabit(habit); +// return view; + + throw new NotImplementedException(""); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java index 59af7a256..858ff7fed 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java @@ -27,14 +27,14 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.widgets.views.GraphWidgetView; import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.HabitStreakView; +import org.isoron.uhabits.ui.habits.show.views.StreakChart; public class StreakWidgetProvider extends BaseWidgetProvider { @Override protected View buildCustomView(Context context, Habit habit) { - HabitStreakView dataView = new HabitStreakView(context); + StreakChart dataView = new StreakChart(context); GraphWidgetView view = new GraphWidgetView(context, dataView); view.setHabit(habit); return view; diff --git a/app/src/main/res/layout/show_habit.xml b/app/src/main/res/layout/show_habit.xml index 9bfd2c569..4173c9094 100644 --- a/app/src/main/res/layout/show_habit.xml +++ b/app/src/main/res/layout/show_habit.xml @@ -31,49 +31,23 @@ style="@style/CardList" android:clipToPadding="false"> - + android:id="@+id/subtitleCard"/> - - - - - - - - - - + android:gravity="center" /> - @@ -113,8 +87,8 @@ style="@style/CardHeader" android:text="@string/best_streaks"/> - @@ -128,7 +102,7 @@ style="@style/CardHeader" android:text="@string/frequency"/> - diff --git a/app/src/main/res/layout/show_habit_preview.xml b/app/src/main/res/layout/show_habit_preview.xml index 4ff01a1a9..252723208 100644 --- a/app/src/main/res/layout/show_habit_preview.xml +++ b/app/src/main/res/layout/show_habit_preview.xml @@ -20,7 +20,6 @@ + app:title="Meditation"/> - - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/show_habit_strength.xml b/app/src/main/res/layout/show_habit_strength.xml new file mode 100644 index 000000000..d72debdf2 --- /dev/null +++ b/app/src/main/res/layout/show_habit_strength.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + \ No newline at end of file From efd0d1e051b925de07c2e324900b6beb9874b821 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 24 Jun 2016 08:39:48 -0400 Subject: [PATCH 035/184] Refactor ShowHabit fragment; break widgets --- app/build.gradle | 4 + .../java/org/isoron/uhabits/BaseViewTest.java | 4 +- .../org/isoron/uhabits/espresso/MainTest.java | 4 +- .../espresso/ShowHabitActivityActions.java | 2 +- .../habits/show/views/FrequencyChartTest.java | 9 +- ...ewTest.java => HabitHistoryChartTest.java} | 48 +++-- .../ui/habits/show/views/ScoreChartTest.java | 1 + .../ui/habits/show/views/StreakChartTest.java | 9 +- .../java/org/isoron/uhabits/models/Habit.java | 20 +- .../ui/habits/edit/HistoryEditorDialog.java | 23 +- .../ui/habits/show/ShowHabitFragment.java | 96 ++------- .../ui/habits/show/ShowHabitHelper.java | 63 ------ .../show/views/cards/FrequencyCard.java | 92 ++++++++ .../ui/habits/show/views/cards/HabitCard.java | 102 +++++++++ .../habits/show/views/cards/HistoryCard.java | 99 +++++++++ .../show/views/{ => cards}/OverviewCard.java | 110 ++++------ .../show/views/{ => cards}/ScoreCard.java | 127 +++-------- .../habits/show/views/cards/StreakCard.java | 99 +++++++++ .../show/views/{ => cards}/SubtitleCard.java | 42 +--- .../views/{ => charts}/FrequencyChart.java | 143 +++++------- .../HabitChart.java} | 4 +- .../HistoryChart.java} | 204 +++++++----------- .../show/views/{ => charts}/ScoreChart.java | 130 +++++------ .../views/{ => charts}/ScrollableChart.java | 2 +- .../show/views/{ => charts}/StreakChart.java | 143 +++++------- .../widgets/CheckmarkWidgetProvider.java | 4 +- .../widgets/FrequencyWidgetProvider.java | 25 ++- .../widgets/HistoryWidgetProvider.java | 27 ++- .../uhabits/widgets/ScoreWidgetProvider.java | 5 +- .../uhabits/widgets/StreakWidgetProvider.java | 25 ++- .../widgets/views/CheckmarkWidgetView.java | 4 +- .../widgets/views/GraphWidgetView.java | 8 +- .../widgets/views/HabitWidgetView.java | 4 +- app/src/main/res/layout/show_habit.xml | 72 ++----- ..._strength.xml => show_habit_frequency.xml} | 25 +-- .../main/res/layout/show_habit_history.xml | 48 +++++ .../main/res/layout/show_habit_preview.xml | 57 ----- app/src/main/res/layout/show_habit_score.xml | 52 +++++ app/src/main/res/layout/show_habit_streak.xml | 31 +++ 39 files changed, 1023 insertions(+), 944 deletions(-) rename app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/{HabitHistoryViewTest.java => HabitHistoryChartTest.java} (69%) delete mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/FrequencyCard.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/HabitCard.java create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/HistoryCard.java rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{ => cards}/OverviewCard.java (79%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{ => cards}/ScoreCard.java (61%) create mode 100644 app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/StreakCard.java rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{ => cards}/SubtitleCard.java (80%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{ => charts}/FrequencyChart.java (83%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{HabitDataView.java => charts/HabitChart.java} (91%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{HistoryView.java => charts/HistoryChart.java} (81%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{ => charts}/ScoreChart.java (92%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{ => charts}/ScrollableChart.java (98%) rename app/src/main/java/org/isoron/uhabits/ui/habits/show/views/{ => charts}/StreakChart.java (78%) rename app/src/main/res/layout/{show_habit_strength.xml => show_habit_frequency.xml} (56%) create mode 100644 app/src/main/res/layout/show_habit_history.xml delete mode 100644 app/src/main/res/layout/show_habit_preview.xml create mode 100644 app/src/main/res/layout/show_habit_score.xml create mode 100644 app/src/main/res/layout/show_habit_streak.xml diff --git a/app/build.gradle b/app/build.gradle index 77ef7c0b3..2b2a8d82b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,3 +91,7 @@ dependencies { exclude group: 'com.android.support' } } + +retrolambda { + defaultMethods true +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java index 930028ed6..f56fff7d7 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseViewTest.java @@ -24,7 +24,7 @@ import android.os.*; import android.view.*; import org.isoron.uhabits.tasks.*; -import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.isoron.uhabits.utils.*; import java.io.*; @@ -209,7 +209,7 @@ public class BaseViewTest extends BaseAndroidTest e.recycle(); } - protected void refreshData(final HabitDataView view) + protected void refreshData(final HabitChart view) { new BaseTask() { diff --git a/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java index ab3cfd775..75798c8aa 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/MainTest.java @@ -135,7 +135,7 @@ public class MainTest onView(withId(R.id.scoreView)).perform(scrollTo(), swipeRight()); - onView(withId(R.id.punchcardView)).perform(scrollTo(), swipeRight()); + onView(withId(R.id.frequencyChart)).perform(scrollTo(), swipeRight()); } /** @@ -234,7 +234,7 @@ public class MainTest clickAtRandomLocations(20)); pressBack(); - onView(withId(R.id.historyView)).perform(scrollTo(), swipeRight(), + onView(withId(R.id.historyChart)).perform(scrollTo(), swipeRight(), swipeLeft()); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/espresso/ShowHabitActivityActions.java b/app/src/androidTest/java/org/isoron/uhabits/espresso/ShowHabitActivityActions.java index 5976ec06f..6475b0890 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/espresso/ShowHabitActivityActions.java +++ b/app/src/androidTest/java/org/isoron/uhabits/espresso/ShowHabitActivityActions.java @@ -31,7 +31,7 @@ public class ShowHabitActivityActions { public static void openHistoryEditor() { - onView(ViewMatchers.withId(R.id.btEditHistory)) + onView(ViewMatchers.withId(R.id.edit)) .perform(scrollTo(), click()); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChartTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChartTest.java index a1ebb663d..2d9adab8a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChartTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChartTest.java @@ -22,8 +22,10 @@ package org.isoron.uhabits.ui.habits.show.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import org.apache.commons.lang3.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,9 +45,10 @@ public class FrequencyChartTest extends BaseViewTest Habit habit = fixtures.createLongHabit(); view = new FrequencyChart(targetContext); - view.setHabit(habit); - refreshData(view); - measureView(dpToPixels(300), dpToPixels(100), view); + throw new NotImplementedException(""); +// view.setHabit(habit); +// refreshData(view); +// measureView(dpToPixels(300), dpToPixels(100), view); } @Test diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryChartTest.java similarity index 69% rename from app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java rename to app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryChartTest.java index 3e180583c..13d453cf4 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/HabitHistoryChartTest.java @@ -22,8 +22,10 @@ package org.isoron.uhabits.ui.habits.show.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import org.apache.commons.lang3.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.isoron.uhabits.utils.DateUtils; import org.junit.Before; import org.junit.Test; @@ -36,11 +38,11 @@ import static org.hamcrest.Matchers.equalTo; @RunWith(AndroidJUnit4.class) @SmallTest -public class HabitHistoryViewTest extends BaseViewTest +public class HabitHistoryChartTest extends BaseViewTest { private Habit habit; - private HistoryView view; + private HistoryChart chart; @Before public void setUp() @@ -50,10 +52,11 @@ public class HabitHistoryViewTest extends BaseViewTest fixtures.purgeHabits(habitList); habit = fixtures.createLongHabit(); - view = new HistoryView(targetContext); - view.setHabit(habit); - measureView(dpToPixels(400), dpToPixels(200), view); - refreshData(view); + chart = new HistoryChart(targetContext); + throw new NotImplementedException(""); +// chart.setHabit(habit); +// measureView(dpToPixels(400), dpToPixels(200), chart); +// refreshData(chart); } @Test @@ -61,10 +64,10 @@ public class HabitHistoryViewTest extends BaseViewTest { int expectedCheckmarkValues[] = habit.getCheckmarks().getAllValues(); - view.setIsEditable(true); - tap(view, 118, 13); // header - tap(view, 336, 60); // tomorrow's square - tap(view, 370, 60); // right axis + chart.setIsEditable(true); + tap(chart, 118, 13); // header + tap(chart, 336, 60); // tomorrow's square + tap(chart, 370, 60); // right axis waitForAsyncTasks(); int actualCheckmarkValues[] = habit.getCheckmarks().getAllValues(); @@ -74,8 +77,8 @@ public class HabitHistoryViewTest extends BaseViewTest @Test public void tapDate_withEditableView() throws Throwable { - view.setIsEditable(true); - tap(view, 340, 40); // today's square + chart.setIsEditable(true); + tap(chart, 340, 40); // today's square waitForAsyncTasks(); long today = DateUtils.getStartOfToday(); @@ -85,8 +88,8 @@ public class HabitHistoryViewTest extends BaseViewTest @Test public void tapDate_withReadOnlyView() throws Throwable { - view.setIsEditable(false); - tap(view, 340, 40); // today's square + chart.setIsEditable(false); + tap(chart, 340, 40); // today's square waitForAsyncTasks(); long today = DateUtils.getStartOfToday(); @@ -96,30 +99,29 @@ public class HabitHistoryViewTest extends BaseViewTest @Test public void testRender() throws Throwable { - assertRenders(view, "HabitHistoryView/render.png"); + assertRenders(chart, "HabitHistoryView/render.png"); } @Test public void testRender_withDataOffset() throws Throwable { - view.onScroll(null, null, -dpToPixels(150), 0); - view.invalidate(); + chart.onScroll(null, null, -dpToPixels(150), 0); + chart.invalidate(); - assertRenders(view, "HabitHistoryView/renderDataOffset.png"); + assertRenders(chart, "HabitHistoryView/renderDataOffset.png"); } @Test public void testRender_withDifferentSize() throws Throwable { - measureView(dpToPixels(200), dpToPixels(200), view); - assertRenders(view, "HabitHistoryView/renderDifferentSize.png"); + measureView(dpToPixels(200), dpToPixels(200), chart); + assertRenders(chart, "HabitHistoryView/renderDifferentSize.png"); } @Test public void testRender_withTransparentBackground() throws Throwable { - view.setIsBackgroundTransparent(true); - assertRenders(view, "HabitHistoryView/renderTransparent.png"); + chart.setIsBackgroundTransparent(true); + assertRenders(chart, "HabitHistoryView/renderTransparent.png"); } - } diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/ScoreChartTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/ScoreChartTest.java index 03f586fa5..861056319 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/ScoreChartTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/ScoreChartTest.java @@ -25,6 +25,7 @@ import android.util.Log; import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.isoron.uhabits.utils.*; import org.junit.Before; import org.junit.Test; diff --git a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/StreakChartTest.java b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/StreakChartTest.java index a9fef9304..333f03607 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/StreakChartTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/ui/habits/show/views/StreakChartTest.java @@ -22,8 +22,10 @@ package org.isoron.uhabits.ui.habits.show.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import org.apache.commons.lang3.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,9 +47,10 @@ public class StreakChartTest extends BaseViewTest view = new StreakChart(targetContext); measureView(dpToPixels(300), dpToPixels(100), view); + throw new NotImplementedException(""); - view.setHabit(habit); - refreshData(view); +// view.setHabit(habit); +// refreshData(view); } @Test @@ -60,7 +63,7 @@ public class StreakChartTest extends BaseViewTest public void testRender_withSmallSize() throws Throwable { measureView(dpToPixels(100), dpToPixels(100), view); - refreshData(view); +// refreshData(view); assertRenders(view, "HabitStreakView/renderSmallSize.png"); } diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index bc85f85c2..4e5696e43 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -24,6 +24,7 @@ import android.support.annotation.*; import org.apache.commons.lang3.builder.*; import org.isoron.uhabits.*; +import org.isoron.uhabits.models.memory.*; import java.util.*; @@ -82,14 +83,8 @@ public class Habit */ public Habit(Habit model) { - HabitsApplication.getComponent().inject(this); - copyFrom(model); - - checkmarks = factory.buildCheckmarkList(this); - streaks = factory.buildStreakList(this); - scores = factory.buildScoreList(this); - repetitions = factory.buildRepetitionList(this); + buildLists(); } /** @@ -100,12 +95,19 @@ public class Habit */ public Habit() { - HabitsApplication.getComponent().inject(this); - this.color = 5; this.archived = false; this.frequency = new Frequency(3, 7); + buildLists(); + } + + private void buildLists() + { + BaseComponent component = HabitsApplication.getComponent(); + if(component == null) factory = new MemoryModelFactory(); + else component.inject(this); + checkmarks = factory.buildCheckmarkList(this); streaks = factory.buildStreakList(this); scores = factory.buildScoreList(this); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java index 183d3d0ed..2b7730721 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/edit/HistoryEditorDialog.java @@ -27,12 +27,13 @@ import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatDialogFragment; import android.util.DisplayMetrics; +import org.apache.commons.lang3.*; import org.isoron.uhabits.HabitsApplication; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.HabitList; import org.isoron.uhabits.tasks.BaseTask; -import org.isoron.uhabits.ui.habits.show.views.HistoryView; +import org.isoron.uhabits.ui.habits.show.views.charts.HistoryChart; import javax.inject.Inject; @@ -43,7 +44,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment private Listener listener; - HistoryView historyView; + HistoryChart historyChart; @Inject HabitList habitList; @@ -53,7 +54,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment { Context context = getActivity(); HabitsApplication.getComponent().inject(this); - historyView = new HistoryView(context, null); + historyChart = new HistoryChart(context, null); if (savedInstanceState != null) { @@ -63,14 +64,16 @@ public class HistoryEditorDialog extends AppCompatDialogFragment int padding = (int) getResources().getDimension(R.dimen.history_editor_padding); - historyView.setPadding(padding, 0, padding, 0); - historyView.setHabit(habit); - historyView.setIsEditable(true); + + if(true) throw new NotImplementedException(""); + historyChart.setPadding(padding, 0, padding, 0); +// historyChart.setHabit(habit); + historyChart.setIsEditable(true); AlertDialog.Builder builder = new AlertDialog.Builder(context); builder .setTitle(R.string.history) - .setView(historyView) + .setView(historyChart) .setPositiveButton(android.R.string.ok, this); refreshData(); @@ -85,7 +88,7 @@ public class HistoryEditorDialog extends AppCompatDialogFragment @Override protected void doInBackground() { - historyView.refreshData(); +// historyChart.refreshData(); } }.execute(); } @@ -112,8 +115,8 @@ public class HistoryEditorDialog extends AppCompatDialogFragment public void setHabit(Habit habit) { - this.habit = habit; - if (historyView != null) historyView.setHabit(habit); +// this.habit = habit; +// if (historyChart != null) historyChart.setHabit(habit); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java index 11be5848f..6b952f9d2 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitFragment.java @@ -26,35 +26,21 @@ import android.view.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.ui.habits.edit.*; -import org.isoron.uhabits.ui.habits.show.views.*; - -import java.util.*; +import org.isoron.uhabits.ui.habits.show.views.cards.*; import butterknife.*; public class ShowHabitFragment extends Fragment - implements ModelObservable.Listener { Habit habit; - int activeColor; - - int inactiveColor; - - private ShowHabitHelper helper; - protected ShowHabitActivity activity; - private List dataViews; - - @BindView(R.id.historyView) - HistoryView historyView; - - @BindView(R.id.punchcardView) - FrequencyChart frequencyChart; + @BindView(R.id.frequencyCard) + FrequencyCard frequencyCard; - @BindView(R.id.streakChart) - StreakChart streakChart; + @BindView(R.id.streakCard) + StreakCard streakCard; @BindView(R.id.subtitleCard) SubtitleCard subtitleCard; @@ -65,6 +51,15 @@ public class ShowHabitFragment extends Fragment @BindView(R.id.strengthCard) ScoreCard scoreCard; + @BindView(R.id.historyCard) + HistoryCard historyCard; + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + { +// inflater.inflate(R.menu.show_habit_fragment, menu); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -74,45 +69,17 @@ public class ShowHabitFragment extends Fragment ButterKnife.bind(this, view); activity = (ShowHabitActivity) getActivity(); - helper = new ShowHabitHelper(this); - habit = activity.getHabit(); - helper.updateColors(); - - createDataViews(); - helper.updateCardHeaders(view); - setHasOptionsMenu(true); - return view; - } - - @OnClick(R.id.btEditHistory) - public void onClickEditHistory() - { - HistoryEditorDialog frag = new HistoryEditorDialog(); - frag.setHabit(habit); - frag.show(getFragmentManager(), "historyEditor"); - } - - private void createDataViews() - { subtitleCard.setHabit(habit); overviewCard.setHabit(habit); scoreCard.setHabit(habit); + historyCard.setHabit(habit); + streakCard.setHabit(habit); + frequencyCard.setHabit(habit); - dataViews = new LinkedList<>(); - dataViews.add(historyView); - dataViews.add(frequencyChart); - dataViews.add(streakChart); - - for (HabitDataView dataView : dataViews) - dataView.setHabit(habit); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) - { -// inflater.inflate(R.menu.show_habit_fragment, menu); + setHasOptionsMenu(true); + return view; } @Override @@ -124,6 +91,7 @@ public class ShowHabitFragment extends Fragment return false; } + private boolean showEditHabitDialog() { if (habit == null) return false; @@ -133,28 +101,4 @@ public class ShowHabitFragment extends Fragment frag.show(getFragmentManager(), "editHabit"); return true; } - - @Override - public void onModelChange() - { - activity.runOnUiThread(() -> { - helper.updateColors(); - helper.updateCardHeaders(getView()); - if (activity != null) activity.setupHabitActionBar(); - }); - } - - @Override - public void onStart() - { - super.onStart(); - habit.getObservable().addListener(this); - } - - @Override - public void onPause() - { - habit.getObservable().removeListener(this); - super.onPause(); - } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java deleted file mode 100644 index 38f2062e4..000000000 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/ShowHabitHelper.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.ui.habits.show; - -import android.view.*; -import android.widget.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.utils.*; - -public class ShowHabitHelper -{ - private ShowHabitFragment fragment; - - public ShowHabitHelper(ShowHabitFragment fragment) - { - this.fragment = fragment; - } - - void updateCardHeaders(View view) - { - updateColor(view, R.id.tvHistory); - updateColor(view, R.id.tvStreaks); - updateColor(view, R.id.tvWeekdayFreq); - updateColor(view, R.id.scoreLabel); - } - - void updateColor(View view, int viewId) - { - if (fragment.habit == null || fragment.activity == null) return; - - TextView textView = (TextView) view.findViewById(viewId); - int androidColor = - ColorUtils.getColor(fragment.activity, fragment.habit.getColor()); - textView.setTextColor(androidColor); - } - - void updateColors() - { - fragment.activeColor = ColorUtils.getColor(fragment.getContext(), - fragment.habit.getColor()); - fragment.inactiveColor = - InterfaceUtils.getStyledColor(fragment.getContext(), - R.attr.mediumContrastTextColor); - } -} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/FrequencyCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/FrequencyCard.java new file mode 100644 index 000000000..f00763d1e --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/FrequencyCard.java @@ -0,0 +1,92 @@ +/* + * 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.ui.habits.show.views.cards; + +import android.content.*; +import android.util.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; +import org.isoron.uhabits.utils.*; + +import java.util.*; + +import butterknife.*; + +public class FrequencyCard extends HabitCard +{ + @BindView(R.id.title) + TextView title; + + @BindView(R.id.frequencyChart) + FrequencyChart chart; + + public FrequencyCard(Context context) + { + super(context); + init(); + } + + public FrequencyCard(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + private void init() + { + inflate(getContext(), R.layout.show_habit_frequency, this); + ButterKnife.bind(this); + + if(isInEditMode()) initEditMode(); + } + + private void initEditMode() + { + int color = ColorUtils.getAndroidTestColor(1); + title.setTextColor(color); + chart.setColor(color); + chart.populateWithRandomData(); + } + + @Override + protected void refreshData() + { + Habit habit = getHabit(); + int color = ColorUtils.getColor(getContext(), habit.getColor()); + + title.setTextColor(color); + chart.setColor(color); + + new BaseTask() + { + @Override + protected void doInBackground() + { + RepetitionList reps = habit.getRepetitions(); + HashMap frequency = reps.getWeekdayFrequency(); + chart.setFrequency(frequency); + } + }.execute(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/HabitCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/HabitCard.java new file mode 100644 index 000000000..45593118d --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/HabitCard.java @@ -0,0 +1,102 @@ +/* + * 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.ui.habits.show.views.cards; + +import android.content.*; +import android.support.annotation.*; +import android.util.*; +import android.widget.*; + +import org.isoron.uhabits.models.*; + +public abstract class HabitCard extends LinearLayout + implements ModelObservable.Listener +{ + @NonNull + private Habit habit; + + public HabitCard(Context context) + { + super(context); + init(); + } + + public HabitCard(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + @NonNull + public Habit getHabit() + { + return habit; + } + + public void setHabit(@NonNull Habit habit) + { + detachFrom(this.habit); + attachTo(habit); + + this.habit = habit; + } + + @Override + public void onModelChange() + { + refreshData(); + } + + @Override + protected void onAttachedToWindow() + { + if(isInEditMode()) return; + + super.onAttachedToWindow(); + refreshData(); + attachTo(habit); + } + + @Override + protected void onDetachedFromWindow() + { + detachFrom(habit); + super.onDetachedFromWindow(); + } + + protected abstract void refreshData(); + + private void attachTo(Habit habit) + { + habit.getObservable().addListener(this); + habit.getRepetitions().getObservable().addListener(this); + } + + private void detachFrom(Habit habit) + { + habit.getRepetitions().getObservable().removeListener(this); + habit.getObservable().removeListener(this); + } + + private void init() + { + if(!isInEditMode()) habit = new Habit(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/HistoryCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/HistoryCard.java new file mode 100644 index 000000000..680a586c8 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/HistoryCard.java @@ -0,0 +1,99 @@ +/* + * 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.ui.habits.show.views.cards; + +import android.content.*; +import android.util.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; +import org.isoron.uhabits.utils.*; + +import butterknife.*; + +public class HistoryCard extends HabitCard +{ + @BindView(R.id.historyChart) + HistoryChart chart; + + @BindView(R.id.title) + TextView title; + + public HistoryCard(Context context) + { + super(context); + init(); + } + + public HistoryCard(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + @OnClick(R.id.edit) + public void onClickEditButton() + { + Log.d("HistoryCard", "onClickEditButton"); + +// HistoryEditorDialog frag = new HistoryEditorDialog(); +// frag.setHabit(habit); +// frag.show(getContext().getFragmentManager(), "historyEditor"); + } + + private void init() + { + inflate(getContext(), R.layout.show_habit_history, this); + ButterKnife.bind(this); + + if (isInEditMode()) initEditMode(); + } + + private void initEditMode() + { + int color = ColorUtils.getAndroidTestColor(1); + title.setTextColor(color); + chart.setColor(color); + chart.populateWithRandomData(); + } + + @Override + protected void refreshData() + { + Habit habit = getHabit(); + int color = ColorUtils.getColor(getContext(), habit.getColor()); + + title.setTextColor(color); + chart.setColor(color); + + new BaseTask() + { + @Override + protected void doInBackground() + { + int checkmarks[] = habit.getCheckmarks().getAllValues(); + chart.setCheckmarks(checkmarks); + } + }.execute(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/OverviewCard.java similarity index 79% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCard.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/OverviewCard.java index 845b0adec..02cdbe554 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/OverviewCard.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/OverviewCard.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.cards; import android.content.*; import android.support.annotation.*; @@ -27,16 +27,13 @@ import android.widget.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.ui.habits.show.views.*; import org.isoron.uhabits.utils.*; import butterknife.*; -public class OverviewCard extends LinearLayout - implements ModelObservable.Listener +public class OverviewCard extends HabitCard { - @Nullable - private Habit habit; - @NonNull private Cache cache; @@ -70,61 +67,19 @@ public class OverviewCard extends LinearLayout } @Override - public void onModelChange() + protected void refreshData() { - refreshCache(); - } - - public void setHabit(@Nullable Habit habit) - { - this.habit = habit; + Habit habit = getHabit(); color = ColorUtils.getColor(getContext(), habit.getColor()); - } - - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - refreshCache(); - if(habit != null) habit.getObservable().addListener(this); - } + refreshColors(); - @Override - protected void onDetachedFromWindow() - { - if(habit != null) habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); - } - - private void init() - { - inflate(getContext(), R.layout.show_habit_overview, this); - ButterKnife.bind(this); - cache = new Cache(); - - if(isInEditMode()) initEditMode(); - } - - private void initEditMode() - { - color = ColorUtils.getAndroidTestColor(1); - cache.todayScore = Score.MAX_VALUE * 0.6f; - cache.lastMonthScore = Score.MAX_VALUE * 0.42f; - cache.lastYearScore = Score.MAX_VALUE * 0.75f; - updateScore(); - } - - private void refreshCache() - { new BaseTask() { @Override protected void doInBackground() { - if(habit == null) return; ScoreList scores = habit.getScores(); - long today = DateUtils.getStartOfToday(); long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; long lastYear = today - 365 * DateUtils.millisecondsInOneDay; @@ -137,25 +92,54 @@ public class OverviewCard extends LinearLayout @Override protected void onPostExecute(Void aVoid) { - updateScore(); + refreshScore(); super.onPostExecute(aVoid); } }.execute(); } - void updateScore() + private String formatPercentageDiff(float percentageDiff) { - float todayPercentage = cache.todayScore / Score.MAX_VALUE; - float monthDiff = todayPercentage - (cache.lastMonthScore / Score.MAX_VALUE); - float yearDiff = todayPercentage - (cache.lastYearScore / Score.MAX_VALUE); + return String.format("%s%.0f%%", (percentageDiff >= 0 ? "+" : "\u2212"), + Math.abs(percentageDiff) * 100); + } - scoreRing.setColor(color); - scoreRing.setPercentage(todayPercentage); + private void init() + { + inflate(getContext(), R.layout.show_habit_overview, this); + ButterKnife.bind(this); + cache = new Cache(); - scoreLabel.setTextColor(color); - scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); + if (isInEditMode()) initEditMode(); + } + private void initEditMode() + { + color = ColorUtils.getAndroidTestColor(1); + cache.todayScore = Score.MAX_VALUE * 0.6f; + cache.lastMonthScore = Score.MAX_VALUE * 0.42f; + cache.lastYearScore = Score.MAX_VALUE * 0.75f; + refreshColors(); + refreshScore(); + } + + private void refreshColors() + { + scoreRing.setColor(color); + scoreLabel.setTextColor(color); title.setTextColor(color); + } + + private void refreshScore() + { + float todayPercentage = cache.todayScore / Score.MAX_VALUE; + float monthDiff = + todayPercentage - (cache.lastMonthScore / Score.MAX_VALUE); + float yearDiff = + todayPercentage - (cache.lastYearScore / Score.MAX_VALUE); + + scoreRing.setPercentage(todayPercentage); + scoreLabel.setText(String.format("%.0f%%", todayPercentage * 100)); monthDiffLabel.setText(formatPercentageDiff(monthDiff)); yearDiffLabel.setText(formatPercentageDiff(yearDiff)); @@ -169,12 +153,6 @@ public class OverviewCard extends LinearLayout postInvalidate(); } - private String formatPercentageDiff(float percentageDiff) - { - return String.format("%s%.0f%%", (percentageDiff >= 0 ? "+" : "\u2212"), - Math.abs(percentageDiff) * 100); - } - private class Cache { public float todayScore; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/ScoreCard.java similarity index 61% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/ScoreCard.java index 3874aaa22..5d9e28578 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreCard.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/ScoreCard.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.cards; import android.content.*; import android.support.annotation.*; @@ -27,14 +27,14 @@ import android.widget.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.isoron.uhabits.utils.*; import java.util.*; import butterknife.*; -public class ScoreCard extends RelativeLayout - implements ModelObservable.Listener +public class ScoreCard extends HabitCard { public static final int[] BUCKET_SIZES = { 1, 7, 31, 92, 365 }; @@ -47,11 +47,6 @@ public class ScoreCard extends RelativeLayout @BindView(R.id.title) TextView title; - @Nullable - private Habit habit; - - private int color; - private int bucketSize; public ScoreCard(Context context) @@ -70,86 +65,64 @@ public class ScoreCard extends RelativeLayout public void onItemSelected(int position) { setBucketSizeFromPosition(position); + refreshData(); } @Override - public void onModelChange() + protected void refreshData() { - refreshData(); - } + Habit habit = getHabit(); + int color = ColorUtils.getColor(getContext(), habit.getColor()); - public void setHabit(@NonNull Habit habit) - { - this.habit = habit; - color = ColorUtils.getColor(getContext(), habit.getColor()); - refreshData(); - } + title.setTextColor(color); + chart.setPrimaryColor(color); - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - if (habit != null) + new BaseTask() { - habit.getObservable().addListener(this); - habit.getScores().getObservable().addListener(this); - } + @Override + protected void doInBackground() + { + List scores; + + if (bucketSize == 1) scores = habit.getScores().getAll(); + else scores = habit.getScores().groupBy(getTruncateField()); + + chart.setScores(scores); + chart.setBucketSize(bucketSize); + } + }.execute(); } - @Override - protected void onDetachedFromWindow() + private int getDefaultSpinnerPosition() { - if (habit != null) - { - habit.getObservable().removeListener(this); - habit.getScores().getObservable().removeListener(this); - } - - super.onDetachedFromWindow(); + if (isInEditMode()) return 0; + return InterfaceUtils.getDefaultScoreSpinnerPosition(getContext()); } @NonNull private DateUtils.TruncateField getTruncateField() { - DateUtils.TruncateField field; + if (bucketSize == 7) return DateUtils.TruncateField.WEEK_NUMBER; + if (bucketSize == 31) return DateUtils.TruncateField.MONTH; + if (bucketSize == 92) return DateUtils.TruncateField.QUARTER; + if (bucketSize == 365) return DateUtils.TruncateField.YEAR; - switch (bucketSize) - { - case 7: - field = DateUtils.TruncateField.WEEK_NUMBER; - break; - - case 365: - field = DateUtils.TruncateField.YEAR; - break; - - case 92: - field = DateUtils.TruncateField.QUARTER; - break; - - default: - Log.e("ScoreCard", - String.format("Unknown bucket size: %d", bucketSize)); - // continue to case 31 - - case 31: - field = DateUtils.TruncateField.MONTH; - break; - } + Log.e("ScoreCard", + String.format("Unknown bucket size: %d", bucketSize)); - return field; + return DateUtils.TruncateField.MONTH; } private void init() { - inflate(getContext(), R.layout.show_habit_strength, this); + inflate(getContext(), R.layout.show_habit_score, this); ButterKnife.bind(this); int defaultPosition = getDefaultSpinnerPosition(); setBucketSizeFromPosition(defaultPosition); spinner.setSelection(defaultPosition); - if(isInEditMode()) + if (isInEditMode()) { spinner.setVisibility(GONE); title.setTextColor(ColorUtils.getAndroidTestColor(1)); @@ -158,41 +131,11 @@ public class ScoreCard extends RelativeLayout } } - private int getDefaultSpinnerPosition() - { - if(isInEditMode()) return 0; - return InterfaceUtils.getDefaultScoreSpinnerPosition(getContext()); - } - - private void refreshData() - { - if (habit == null) return; - - title.setTextColor(color); - chart.setPrimaryColor(color); - - new BaseTask() - { - @Override - protected void doInBackground() - { - List scores; - - if (bucketSize == 1) scores = habit.getScores().getAll(); - else scores = habit.getScores().groupBy(getTruncateField()); - - chart.setScores(scores); - chart.setBucketSize(bucketSize); - } - }.execute(); - } - private void setBucketSizeFromPosition(int position) { - if(isInEditMode()) return; + if (isInEditMode()) return; InterfaceUtils.setDefaultScoreSpinnerPosition(getContext(), position); bucketSize = BUCKET_SIZES[position]; - refreshData(); } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/StreakCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/StreakCard.java new file mode 100644 index 000000000..41869a59c --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/StreakCard.java @@ -0,0 +1,99 @@ +/* + * 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.ui.habits.show.views.cards; + +import android.content.*; +import android.util.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.tasks.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; +import org.isoron.uhabits.utils.*; + +import java.util.*; + +import butterknife.*; + +public class StreakCard extends HabitCard +{ + @BindView(R.id.title) + TextView title; + + @BindView(R.id.streakChart) + StreakChart streakChart; + + public StreakCard(Context context) + { + super(context); + init(); + } + + public StreakCard(Context context, AttributeSet attrs) + { + super(context, attrs); + init(); + } + + private void init() + { + inflate(getContext(), R.layout.show_habit_streak, this); + ButterKnife.bind(this); + setOrientation(VERTICAL); + if(isInEditMode()) initEditMode(); + } + + private void initEditMode() + { + int color = ColorUtils.getAndroidTestColor(1); + title.setTextColor(color); + streakChart.setColor(color); + streakChart.populateWithRandomData(); + } + + @Override + protected void refreshData() + { + Habit habit = getHabit(); + int color = ColorUtils.getColor(getContext(), habit.getColor()); + + title.setTextColor(color); + streakChart.setColor(color); + + new BaseTask() + { + public List streaks; + + @Override + protected void doInBackground() + { + streaks = habit.getStreaks().getBest(10); + } + + @Override + protected void onPostExecute(Void aVoid) + { + streakChart.setStreaks(streaks); + super.onPostExecute(aVoid); + } + }.execute(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCard.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/SubtitleCard.java similarity index 80% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCard.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/SubtitleCard.java index dbc18f530..b37387e05 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/SubtitleCard.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/cards/SubtitleCard.java @@ -17,12 +17,11 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.cards; import android.annotation.*; import android.content.*; import android.content.res.*; -import android.support.annotation.*; import android.util.*; import android.view.*; import android.widget.*; @@ -33,8 +32,7 @@ import org.isoron.uhabits.utils.*; import butterknife.*; -public class SubtitleCard extends LinearLayout - implements ModelObservable.Listener +public class SubtitleCard extends HabitCard { @BindView(R.id.questionLabel) TextView questionLabel; @@ -45,9 +43,6 @@ public class SubtitleCard extends LinearLayout @BindView(R.id.reminderLabel) TextView reminderLabel; - @Nullable - private Habit habit; - public SubtitleCard(Context context) { super(context); @@ -60,32 +55,6 @@ public class SubtitleCard extends LinearLayout init(); } - @Override - public void onModelChange() - { - refreshData(); - } - - public void setHabit(@Nullable Habit habit) - { - this.habit = habit; - refreshData(); - } - - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - if (habit != null) habit.getObservable().addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - if (habit != null) habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); - } - private void init() { Context context = getContext(); @@ -103,9 +72,10 @@ public class SubtitleCard extends LinearLayout reminderLabel.setText("08:00"); } - private void refreshData() + @Override + protected void refreshData() { - if (habit == null) return; + Habit habit = getHabit(); int color = ColorUtils.getColor(getContext(), habit.getColor()); reminderLabel.setText(getResources().getString(R.string.reminder_off)); @@ -123,7 +93,7 @@ public class SubtitleCard extends LinearLayout postInvalidate(); } - String toText(Frequency freq) + private String toText(Frequency freq) { Resources resources = getResources(); Integer num = freq.getNumerator(); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChart.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/FrequencyChart.java similarity index 83% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChart.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/FrequencyChart.java index df069c455..540574f60 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/FrequencyChart.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/FrequencyChart.java @@ -17,29 +17,25 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.charts; import android.content.*; import android.graphics.*; +import android.support.annotation.*; import android.util.*; import org.isoron.uhabits.*; -import org.isoron.uhabits.models.*; -import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.utils.*; import java.text.*; import java.util.*; public class FrequencyChart extends ScrollableChart - implements HabitDataView, ModelObservable.Listener { private Paint pGrid; private float em; - private Habit habit; - private SimpleDateFormat dfMonth; private SimpleDateFormat dfYear; @@ -68,6 +64,7 @@ public class FrequencyChart extends ScrollableChart private boolean isBackgroundTransparent; + @NonNull private HashMap frequency; public FrequencyChart(Context context) @@ -79,42 +76,30 @@ public class FrequencyChart extends ScrollableChart public FrequencyChart(Context context, AttributeSet attrs) { super(context, attrs); - this.primaryColor = ColorUtils.getColor(getContext(), 7); this.frequency = new HashMap<>(); init(); } - @Override - public void onModelChange() - { - refreshData(); - } - - public void refreshData() + public void setColor(int color) { - if (isInEditMode()) generateRandomData(); - else if (habit != null) - { - frequency = habit.getRepetitions().getWeekdayFrequency(); - createColors(); - } - + this.primaryColor = color; + initColors(); postInvalidate(); } - public void setHabit(Habit habit) + public void setFrequency(HashMap frequency) { - this.habit = habit; - createColors(); + this.frequency = frequency; + postInvalidate(); } public void setIsBackgroundTransparent(boolean isBackgroundTransparent) { this.isBackgroundTransparent = isBackgroundTransparent; - createColors(); + initColors(); } - protected void createPaints() + protected void initPaints() { pText = new Paint(); pText.setAntiAlias(true); @@ -127,30 +112,6 @@ public class FrequencyChart extends ScrollableChart pGrid.setAntiAlias(true); } - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - new BaseTask() - { - @Override - protected void doInBackground() - { - refreshData(); - } - }.execute(); - habit.getObservable().addListener(this); - habit.getCheckmarks().observable.addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - habit.getCheckmarks().observable.removeListener(this); - habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); - } - @Override protected void onDraw(Canvas canvas) { @@ -214,26 +175,6 @@ public class FrequencyChart extends ScrollableChart paddingTop = 0; } - private void createColors() - { - if (habit != null) - { - this.primaryColor = - ColorUtils.getColor(getContext(), habit.getColor()); - } - - textColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.mediumContrastTextColor); - gridColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.lowContrastTextColor); - - colors = new int[4]; - colors[0] = gridColor; - colors[3] = primaryColor; - colors[1] = ColorUtils.mixColors(colors[0], colors[3], 0.66f); - colors[2] = ColorUtils.mixColors(colors[0], colors[3], 0.33f); - } - private void drawColumn(Canvas canvas, RectF rect, GregorianCalendar date) { Integer values[] = frequency.get(date.getTimeInMillis()); @@ -301,24 +242,6 @@ public class FrequencyChart extends ScrollableChart canvas.drawCircle(rect.centerX(), rect.centerY(), radius, pGraph); } - private void generateRandomData() - { - GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); - date.set(Calendar.DAY_OF_MONTH, 1); - Random rand = new Random(); - frequency.clear(); - - for (int i = 0; i < 40; i++) - { - Integer values[] = new Integer[7]; - for (int j = 0; j < 7; j++) - values[j] = rand.nextInt(5); - - frequency.put(date.getTimeInMillis(), values); - date.add(Calendar.MONTH, -1); - } - } - private float getMaxMonthWidth() { float maxMonthWidth = 0; @@ -336,13 +259,53 @@ public class FrequencyChart extends ScrollableChart private void init() { - createPaints(); - createColors(); + initPaints(); + initColors(); + initDateFormats(); + initRects(); + } + + private void initColors() + { + textColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + gridColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + + colors = new int[4]; + colors[0] = gridColor; + colors[3] = primaryColor; + colors[1] = ColorUtils.mixColors(colors[0], colors[3], 0.66f); + colors[2] = ColorUtils.mixColors(colors[0], colors[3], 0.33f); + } + private void initDateFormats() + { dfMonth = DateUtils.getDateFormat("MMM"); dfYear = DateUtils.getDateFormat("yyyy"); + } + private void initRects() + { rect = new RectF(); prevRect = new RectF(); } + + public void populateWithRandomData() + { + GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); + date.set(Calendar.DAY_OF_MONTH, 1); + Random rand = new Random(); + frequency.clear(); + + for (int i = 0; i < 40; i++) + { + Integer values[] = new Integer[7]; + for (int j = 0; j < 7; j++) + values[j] = rand.nextInt(5); + + frequency.put(date.getTimeInMillis(), values); + date.add(Calendar.MONTH, -1); + } + } } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/HabitChart.java similarity index 91% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/HabitChart.java index 223703393..0f867689c 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HabitDataView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/HabitChart.java @@ -17,11 +17,11 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.charts; import org.isoron.uhabits.models.Habit; -public interface HabitDataView +public interface HabitChart { void setHabit(Habit habit); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HistoryView.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/HistoryChart.java similarity index 81% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HistoryView.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/HistoryChart.java index 0918bbaf6..d76294549 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/HistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/HistoryChart.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.charts; import android.content.*; import android.graphics.*; @@ -26,19 +26,13 @@ import android.util.*; import android.view.*; import org.isoron.uhabits.*; -import org.isoron.uhabits.models.*; -import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.utils.*; import java.text.*; import java.util.*; -public class HistoryView extends ScrollableChart implements HabitDataView, - ToggleRepetitionTask.Listener, - ModelObservable.Listener +public class HistoryChart extends ScrollableChart { - private Habit habit; - private int[] checkmarks; private Paint pSquareBg, pSquareFg, pTextHeader; @@ -88,13 +82,13 @@ public class HistoryView extends ScrollableChart implements HabitDataView, private float headerOverflow = 0; - public HistoryView(Context context) + public HistoryChart(Context context) { super(context); init(); } - public HistoryView(Context context, AttributeSet attrs) + public HistoryChart(Context context, AttributeSet attrs) { super(context, attrs); init(); @@ -106,12 +100,6 @@ public class HistoryView extends ScrollableChart implements HabitDataView, onSingleTapUp(e); } - @Override - public void onModelChange() - { - refreshData(); - } - @Override public boolean onSingleTapUp(MotionEvent e) { @@ -126,59 +114,48 @@ public class HistoryView extends ScrollableChart implements HabitDataView, final Long timestamp = positionToTimestamp(x, y); if (timestamp == null) return false; - ToggleRepetitionTask task = new ToggleRepetitionTask(habit, timestamp); - task.setListener(this); - task.execute(); +// ToggleRepetitionTask task = new ToggleRepetitionTask(habit, timestamp); +// task.setListener(this); +// task.execute(); return true; } - @Override - public void onToggleRepetitionFinished() + public void populateWithRandomData() { - new BaseTask() - { - @Override - protected void doInBackground() - { - refreshData(); - } + Random random = new Random(); + checkmarks = new int[100]; - @Override - protected void onPostExecute(Void aVoid) - { - invalidate(); - super.onPostExecute(null); - } - }.execute(); - } + for (int i = 0; i < 100; i++) + if (random.nextFloat() < 0.3) checkmarks[i] = 2; - @Override - public void refreshData() - { - if (isInEditMode()) generateRandomData(); - else + for (int i = 0; i < 100 - 7; i++) { - if (habit == null) return; - checkmarks = habit.getCheckmarks().getAllValues(); - createColors(); + int count = 0; + for (int j = 0; j < 7; j++) + if (checkmarks[i + j] != 0) count++; + + if (count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1); } + } - updateDate(); + public void setCheckmarks(int[] checkmarks) + { + this.checkmarks = checkmarks; postInvalidate(); } - @Override - public void setHabit(Habit habit) + public void setColor(int color) { - this.habit = habit; - createColors(); + this.primaryColor = color; + initColors(); + postInvalidate(); } public void setIsBackgroundTransparent(boolean isBackgroundTransparent) { this.isBackgroundTransparent = isBackgroundTransparent; - createColors(); + initColors(); } public void setIsEditable(boolean isEditable) @@ -186,7 +163,7 @@ public class HistoryView extends ScrollableChart implements HabitDataView, this.isEditable = isEditable; } - protected void createPaints() + protected void initPaints() { pTextHeader = new Paint(); pTextHeader.setTextAlign(Align.LEFT); @@ -199,30 +176,6 @@ public class HistoryView extends ScrollableChart implements HabitDataView, pSquareFg.setTextAlign(Align.CENTER); } - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - new BaseTask() - { - @Override - protected void doInBackground() - { - refreshData(); - } - }.execute(); - habit.getObservable().addListener(this); - habit.getCheckmarks().observable.addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - habit.getCheckmarks().observable.removeListener(this); - habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); - } - @Override protected void onDraw(Canvas canvas) { @@ -290,41 +243,6 @@ public class HistoryView extends ScrollableChart implements HabitDataView, updateDate(); } - private void createColors() - { - if (habit != null) this.primaryColor = - ColorUtils.getColor(getContext(), habit.getColor()); - - if (isBackgroundTransparent) - primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f); - - int red = Color.red(primaryColor); - int green = Color.green(primaryColor); - int blue = Color.blue(primaryColor); - - if (isBackgroundTransparent) - { - colors = new int[3]; - colors[0] = Color.argb(16, 255, 255, 255); - colors[1] = Color.argb(128, red, green, blue); - colors[2] = primaryColor; - textColor = Color.WHITE; - reverseTextColor = Color.WHITE; - } - else - { - colors = new int[3]; - colors[0] = InterfaceUtils.getStyledColor(getContext(), - R.attr.lowContrastTextColor); - colors[1] = Color.argb(127, red, green, blue); - colors[2] = primaryColor; - textColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.mediumContrastTextColor); - reverseTextColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.highContrastReverseTextColor); - } - } - private void drawAxis(Canvas canvas, RectF location) { float verticalOffset = pTextHeader.getFontSpacing() * 0.4f; @@ -398,24 +316,6 @@ public class HistoryView extends ScrollableChart implements HabitDataView, location.centerY() + squareTextOffset, pSquareFg); } - private void generateRandomData() - { - Random random = new Random(); - checkmarks = new int[100]; - - for (int i = 0; i < 100; i++) - if (random.nextFloat() < 0.3) checkmarks[i] = 2; - - for (int i = 0; i < 100 - 7; i++) - { - int count = 0; - for (int j = 0; j < 7; j++) - if (checkmarks[i + j] != 0) count++; - - if (count >= 3) checkmarks[i] = Math.max(checkmarks[i], 1); - } - } - private float getWeekdayLabelWidth() { float width = 0; @@ -428,15 +328,55 @@ public class HistoryView extends ScrollableChart implements HabitDataView, private void init() { - createColors(); - createPaints(); - isEditable = false; checkmarks = new int[0]; - primaryColor = ColorUtils.getColor(getContext(), 7); + + initColors(); + initPaints(); + initDateFormats(); + initRects(); + } + + private void initColors() + { + if (isBackgroundTransparent) + primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f); + + int red = Color.red(primaryColor); + int green = Color.green(primaryColor); + int blue = Color.blue(primaryColor); + + if (isBackgroundTransparent) + { + colors = new int[3]; + colors[0] = Color.argb(16, 255, 255, 255); + colors[1] = Color.argb(128, red, green, blue); + colors[2] = primaryColor; + textColor = Color.WHITE; + reverseTextColor = Color.WHITE; + } + else + { + colors = new int[3]; + colors[0] = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + colors[1] = Color.argb(127, red, green, blue); + colors[2] = primaryColor; + textColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + reverseTextColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.highContrastReverseTextColor); + } + } + + private void initDateFormats() + { dfMonth = DateUtils.getDateFormat("MMM"); dfYear = DateUtils.getDateFormat("yyyy"); + } + private void initRects() + { baseLocation = new RectF(); } diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreChart.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/ScoreChart.java similarity index 92% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreChart.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/ScoreChart.java index b641dd225..983c22af9 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScoreChart.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/ScoreChart.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.charts; import android.content.*; import android.graphics.*; @@ -31,6 +31,8 @@ import org.isoron.uhabits.utils.*; import java.text.*; import java.util.*; +import static org.isoron.uhabits.utils.InterfaceUtils.*; + public class ScoreChart extends ScrollableChart { private static final PorterDuffXfermode XFERMODE_CLEAR = @@ -72,6 +74,7 @@ public class ScoreChart extends ScrollableChart private int primaryColor; + @Deprecated private int bucketSize = 7; private int backgroundColor; @@ -100,6 +103,26 @@ public class ScoreChart extends ScrollableChart init(); } + public void populateWithRandomData() + { + Random random = new Random(); + scores = new LinkedList<>(); + + int previous = Score.MAX_VALUE / 2; + long timestamp = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; + + for (int i = 1; i < 100; i++) + { + int step = Score.MAX_VALUE / 10; + int current = previous + random.nextInt(step * 2) - step; + current = Math.max(0, Math.min(Score.MAX_VALUE, current)); + scores.add(new Score(timestamp, current)); + previous = current; + timestamp -= day; + } + } + @Deprecated public void setBucketSize(int bucketSize) { @@ -110,7 +133,7 @@ public class ScoreChart extends ScrollableChart public void setIsTransparencyEnabled(boolean enabled) { this.isTransparencyEnabled = enabled; - createColors(); + initColors(); requestLayout(); } @@ -126,19 +149,6 @@ public class ScoreChart extends ScrollableChart postInvalidate(); } - protected void createPaints() - { - pText = new Paint(); - pText.setAntiAlias(true); - - pGraph = new Paint(); - pGraph.setTextAlign(Paint.Align.CENTER); - pGraph.setAntiAlias(true); - - pGrid = new Paint(); - pGrid.setAntiAlias(true); - } - @Override protected void onDraw(Canvas canvas) { @@ -240,7 +250,7 @@ public class ScoreChart extends ScrollableChart columnHeight = 8 * baseSize; - float minStrokeWidth = InterfaceUtils.dpToPixels(getContext(), 1); + float minStrokeWidth = dpToPixels(getContext(), 1); pGraph.setTextSize(baseSize * 0.5f); pGraph.setStrokeWidth(baseSize * 0.1f); pGrid.setStrokeWidth(Math.min(minStrokeWidth, baseSize * 0.05f)); @@ -248,17 +258,6 @@ public class ScoreChart extends ScrollableChart if (isTransparencyEnabled) initCache(width, height); } - private void createColors() - { - primaryColor = Color.BLACK; - textColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.mediumContrastTextColor); - gridColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.lowContrastTextColor); - backgroundColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.cardBackgroundColor); - } - private void drawFooter(Canvas canvas, RectF rect, long currentDate) { String yearText = dfYear.format(currentDate); @@ -340,7 +339,7 @@ public class ScoreChart extends ScrollableChart private void drawMarker(Canvas canvas, RectF rect) { - rect.inset(baseSize * 0.15f, baseSize * 0.15f); + rect.inset(baseSize * 0.225f, baseSize * 0.225f); setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor); canvas.drawOval(rect, pGraph); @@ -348,33 +347,13 @@ public class ScoreChart extends ScrollableChart setModeOrColor(pGraph, XFERMODE_SRC, primaryColor); canvas.drawOval(rect, pGraph); - rect.inset(baseSize * 0.1f, baseSize * 0.1f); - setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor); - canvas.drawOval(rect, pGraph); +// rect.inset(baseSize * 0.1f, baseSize * 0.1f); +// setModeOrColor(pGraph, XFERMODE_CLEAR, backgroundColor); +// canvas.drawOval(rect, pGraph); if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC); } - public void populateWithRandomData() - { - Random random = new Random(); - scores = new LinkedList<>(); - - int previous = Score.MAX_VALUE / 2; - long timestamp = DateUtils.getStartOfToday(); - long day = DateUtils.millisecondsInOneDay; - - for (int i = 1; i < 100; i++) - { - int step = Score.MAX_VALUE / 10; - int current = previous + random.nextInt(step * 2) - step; - current = Math.max(0, Math.min(Score.MAX_VALUE, current)); - scores.add(new Score(timestamp, current)); - previous = current; - timestamp -= day; - } - } - private float getMaxDayWidth() { float maxDayWidth = 0; @@ -407,15 +386,10 @@ public class ScoreChart extends ScrollableChart private void init() { - createPaints(); - createColors(); - - dfYear = DateUtils.getDateFormat("yyyy"); - dfMonth = DateUtils.getDateFormat("MMM"); - dfDay = DateUtils.getDateFormat("d"); - - rect = new RectF(); - prevRect = new RectF(); + initPaints(); + initColors(); + initDateFormats(); + initRects(); } private void initCache(int width, int height) @@ -426,6 +400,42 @@ public class ScoreChart extends ScrollableChart cacheCanvas = new Canvas(drawingCache); } + private void initColors() + { + Context context = getContext(); + + primaryColor = Color.BLACK; + textColor = getStyledColor(context, R.attr.mediumContrastTextColor); + gridColor = getStyledColor(context, R.attr.lowContrastTextColor); + backgroundColor = getStyledColor(context, R.attr.cardBackgroundColor); + } + + private void initDateFormats() + { + dfYear = DateUtils.getDateFormat("yyyy"); + dfMonth = DateUtils.getDateFormat("MMM"); + dfDay = DateUtils.getDateFormat("d"); + } + + private void initPaints() + { + pText = new Paint(); + pText.setAntiAlias(true); + + pGraph = new Paint(); + pGraph.setTextAlign(Paint.Align.CENTER); + pGraph.setAntiAlias(true); + + pGrid = new Paint(); + pGrid.setAntiAlias(true); + } + + private void initRects() + { + rect = new RectF(); + prevRect = new RectF(); + } + private void setModeOrColor(Paint p, PorterDuffXfermode mode, int color) { if (isTransparencyEnabled) p.setXfermode(mode); diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableChart.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/ScrollableChart.java similarity index 98% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableChart.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/ScrollableChart.java index 9e4cdea43..a60f03434 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/ScrollableChart.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/ScrollableChart.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.charts; import android.animation.*; import android.content.*; diff --git a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/StreakChart.java b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/StreakChart.java similarity index 78% rename from app/src/main/java/org/isoron/uhabits/ui/habits/show/views/StreakChart.java rename to app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/StreakChart.java index bca5499a7..bd678a265 100644 --- a/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/StreakChart.java +++ b/app/src/main/java/org/isoron/uhabits/ui/habits/show/views/charts/StreakChart.java @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.ui.habits.show.views; +package org.isoron.uhabits.ui.habits.show.views.charts; import android.content.*; import android.graphics.*; @@ -26,17 +26,13 @@ import android.view.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.tasks.*; import org.isoron.uhabits.utils.*; import java.text.*; import java.util.*; public class StreakChart extends View - implements HabitDataView, ModelObservable.Listener { - private Habit habit; - private Paint paint; private long minLength; @@ -67,8 +63,6 @@ public class StreakChart extends View private boolean shouldShowLabels; - private int maxStreakCount; - private int textColor; private int reverseTextColor; @@ -82,68 +76,44 @@ public class StreakChart extends View public StreakChart(Context context, AttributeSet attrs) { super(context, attrs); - this.primaryColor = ColorUtils.getColor(getContext(), 7); init(); } - @Override - public void onModelChange() - { - refreshData(); - } - - @Override - public void refreshData() - { - if (habit == null) return; - streaks = habit.getStreaks().getBest(maxStreakCount); - createColors(); - updateMaxMin(); - postInvalidate(); - } - - @Override - public void setHabit(Habit habit) - { - this.habit = habit; - createColors(); - } - public void setIsBackgroundTransparent(boolean isBackgroundTransparent) { this.isBackgroundTransparent = isBackgroundTransparent; - createColors(); + initColors(); } - protected void createPaints() + public void setStreaks(List streaks) { - paint = new Paint(); - paint.setTextAlign(Paint.Align.CENTER); - paint.setAntiAlias(true); + this.streaks = streaks; + initColors(); + updateMaxMinLengths(); + requestLayout(); } - @Override - protected void onAttachedToWindow() + public void populateWithRandomData() { - super.onAttachedToWindow(); - new BaseTask() + long day = DateUtils.millisecondsInOneDay; + long start = DateUtils.getStartOfToday(); + LinkedList streaks = new LinkedList<>(); + + for(int i = 0; i < 10; i++) { - @Override - protected void doInBackground() - { - refreshData(); - } - }.execute(); - habit.getObservable().addListener(this); - habit.getStreaks().getObservable().addListener(this); + int length = new Random().nextInt(100); + long end = start + length * day; + streaks.add(new Streak(start, end)); + start = end + day; + } + + setStreaks(streaks); } - @Override - protected void onDetachedFromWindow() + public void setColor(int color) { - habit.getStreaks().getObservable().removeListener(this); - habit.getObservable().removeListener(this); - super.onDetachedFromWindow(); + this.primaryColor = color; + postInvalidate(); } @Override @@ -162,11 +132,14 @@ public class StreakChart extends View } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + protected void onMeasure(int widthSpec, int heightSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - setMeasuredDimension(width, height); + int width = MeasureSpec.getSize(widthSpec); + int height = streaks.size() * baseSize; + + heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); + setMeasuredDimension(widthSpec, heightSpec); } @Override @@ -175,7 +148,6 @@ public class StreakChart extends View int oldWidth, int oldHeight) { - maxStreakCount = height / baseSize; this.width = width; float minTextSize = getResources().getDimension(R.dimen.tinyTextSize); @@ -188,28 +160,7 @@ public class StreakChart extends View em = paint.getFontSpacing(); textMargin = 0.5f * em; - updateMaxMin(); - } - - private void createColors() - { - if (habit != null) this.primaryColor = - ColorUtils.getColor(getContext(), habit.getColor()); - - int red = Color.red(primaryColor); - int green = Color.green(primaryColor); - int blue = Color.blue(primaryColor); - - colors = new int[4]; - colors[3] = primaryColor; - colors[2] = Color.argb(192, red, green, blue); - colors[1] = Color.argb(96, red, green, blue); - colors[0] = InterfaceUtils.getStyledColor(getContext(), - R.attr.lowContrastTextColor); - textColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.mediumContrastTextColor); - reverseTextColor = InterfaceUtils.getStyledColor(getContext(), - R.attr.highContrastReverseTextColor); + updateMaxMinLengths(); } private void drawRow(Canvas canvas, Streak streak, RectF rect) @@ -256,18 +207,42 @@ public class StreakChart extends View private void init() { - createPaints(); - createColors(); + initPaints(); + initColors(); streaks = Collections.emptyList(); dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); rect = new RectF(); - maxStreakCount = 10; baseSize = getResources().getDimensionPixelSize(R.dimen.baseSize); } + private void initColors() + { + int red = Color.red(primaryColor); + int green = Color.green(primaryColor); + int blue = Color.blue(primaryColor); + + colors = new int[4]; + colors[3] = primaryColor; + colors[2] = Color.argb(192, red, green, blue); + colors[1] = Color.argb(96, red, green, blue); + colors[0] = InterfaceUtils.getStyledColor(getContext(), + R.attr.lowContrastTextColor); + textColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.mediumContrastTextColor); + reverseTextColor = InterfaceUtils.getStyledColor(getContext(), + R.attr.highContrastReverseTextColor); + } + + private void initPaints() + { + paint = new Paint(); + paint.setTextAlign(Paint.Align.CENTER); + paint.setAntiAlias(true); + } + private int percentageToColor(float percentage) { if (percentage >= 1.0f) return colors[3]; @@ -276,7 +251,7 @@ public class StreakChart extends View return colors[0]; } - private void updateMaxMin() + private void updateMaxMinLengths() { maxLength = 0; minLength = Long.MAX_VALUE; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java index e37f59bf1..aaf1102cb 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java @@ -24,7 +24,7 @@ import android.view.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.isoron.uhabits.widgets.views.*; public class CheckmarkWidgetProvider extends BaseWidgetProvider @@ -65,7 +65,7 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider @Override protected void refreshCustomViewData(View view) { - ((HabitDataView) view).refreshData(); + ((HabitChart) view).refreshData(); } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java index 0b7b12610..877430665 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java @@ -19,16 +19,14 @@ package org.isoron.uhabits.widgets; -import android.app.PendingIntent; -import android.content.Context; -import android.view.View; +import android.app.*; +import android.content.*; +import android.view.*; -import org.isoron.uhabits.HabitBroadcastReceiver; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.widgets.views.GraphWidgetView; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.FrequencyChart; +import org.apache.commons.lang3.*; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; public class FrequencyWidgetProvider extends BaseWidgetProvider { @@ -36,15 +34,16 @@ public class FrequencyWidgetProvider extends BaseWidgetProvider protected View buildCustomView(Context context, Habit habit) { FrequencyChart dataView = new FrequencyChart(context); - GraphWidgetView view = new GraphWidgetView(context, dataView); - view.setHabit(habit); - return view; + throw new NotImplementedException(""); +// GraphWidgetView view = new GraphWidgetView(context, dataView); +// view.setHabit(habit); +// return view; } @Override protected void refreshCustomViewData(View view) { - ((HabitDataView) view).refreshData(); + ((HabitChart) view).refreshData(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java index 5ad306198..ebad49af7 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java @@ -18,32 +18,31 @@ */ package org.isoron.uhabits.widgets; -import android.app.PendingIntent; -import android.content.Context; -import android.view.View; +import android.app.*; +import android.content.*; +import android.view.*; -import org.isoron.uhabits.HabitBroadcastReceiver; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.widgets.views.GraphWidgetView; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.HistoryView; +import org.apache.commons.lang3.*; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; public class HistoryWidgetProvider extends BaseWidgetProvider { @Override protected View buildCustomView(Context context, Habit habit) { - HistoryView dataView = new HistoryView(context); - GraphWidgetView view = new GraphWidgetView(context, dataView); - view.setHabit(habit); - return view; + throw new NotImplementedException(""); +// HistoryChart dataView = new HistoryChart(context); +// GraphWidgetView view = new GraphWidgetView(context, dataView); +// view.setHabit(habit); +// return view; } @Override protected void refreshCustomViewData(View view) { - ((HabitDataView) view).refreshData(); + ((HabitChart) view).refreshData(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java index 63adea38e..61a946aa5 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java @@ -25,7 +25,8 @@ import android.view.*; import org.apache.commons.lang3.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.ui.habits.show.views.cards.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.isoron.uhabits.utils.*; public class ScoreWidgetProvider extends BaseWidgetProvider @@ -50,7 +51,7 @@ public class ScoreWidgetProvider extends BaseWidgetProvider @Override protected void refreshCustomViewData(View view) { - ((HabitDataView) view).refreshData(); + ((HabitChart) view).refreshData(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java index 858ff7fed..c7f10fc8d 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java @@ -18,16 +18,14 @@ */ package org.isoron.uhabits.widgets; -import android.app.PendingIntent; -import android.content.Context; -import android.view.View; +import android.app.*; +import android.content.*; +import android.view.*; -import org.isoron.uhabits.HabitBroadcastReceiver; -import org.isoron.uhabits.R; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.widgets.views.GraphWidgetView; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; -import org.isoron.uhabits.ui.habits.show.views.StreakChart; +import org.apache.commons.lang3.*; +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; public class StreakWidgetProvider extends BaseWidgetProvider { @@ -35,15 +33,16 @@ public class StreakWidgetProvider extends BaseWidgetProvider protected View buildCustomView(Context context, Habit habit) { StreakChart dataView = new StreakChart(context); - GraphWidgetView view = new GraphWidgetView(context, dataView); - view.setHabit(habit); - return view; + throw new NotImplementedException(""); +// GraphWidgetView view = new GraphWidgetView(context, dataView); +// view.setHabit(habit); +// return view; } @Override protected void refreshCustomViewData(View view) { - ((HabitDataView) view).refreshData(); + ((HabitChart) view).refreshData(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java b/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java index d1893d536..33d788d33 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java @@ -30,13 +30,13 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.models.Checkmark; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.charts.HabitChart; import org.isoron.uhabits.ui.habits.show.views.RingView; import org.isoron.uhabits.utils.ColorUtils; import org.isoron.uhabits.utils.InterfaceUtils; public class CheckmarkWidgetView extends HabitWidgetView - implements HabitDataView + implements HabitChart { private int activeColor; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java b/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java index 621e00b18..428d96c94 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java @@ -27,15 +27,15 @@ import android.widget.TextView; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.ui.habits.show.views.HabitDataView; +import org.isoron.uhabits.ui.habits.show.views.charts.HabitChart; -public class GraphWidgetView extends HabitWidgetView implements HabitDataView +public class GraphWidgetView extends HabitWidgetView implements HabitChart { - private final HabitDataView dataView; + private final HabitChart dataView; private TextView title; - public GraphWidgetView(Context context, HabitDataView dataView) + public GraphWidgetView(Context context, HabitChart dataView) { super(context); this.dataView = dataView; diff --git a/app/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java b/app/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java index f7ca8131a..900a2e823 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java @@ -30,13 +30,13 @@ import android.widget.*; import org.isoron.uhabits.*; import org.isoron.uhabits.models.*; -import org.isoron.uhabits.ui.habits.show.views.*; +import org.isoron.uhabits.ui.habits.show.views.charts.*; import org.isoron.uhabits.utils.*; import java.util.*; public abstract class HabitWidgetView extends FrameLayout - implements HabitDataView + implements HabitChart { @Nullable protected InsetDrawable background; diff --git a/app/src/main/res/layout/show_habit.xml b/app/src/main/res/layout/show_habit.xml index 4173c9094..d822a5cc2 100644 --- a/app/src/main/res/layout/show_habit.xml +++ b/app/src/main/res/layout/show_habit.xml @@ -19,7 +19,6 @@ - @@ -39,74 +38,29 @@ - - - - - - - - -