diff --git a/docs/GUIDELINES.md b/docs/GUIDELINES.md index 0658f4147..c24b375cb 100644 --- a/docs/GUIDELINES.md +++ b/docs/GUIDELINES.md @@ -49,7 +49,7 @@ Further resources: ## Code Style -For Kotlin, we follow [ktlint](https://ktlint.github.io/) style with default settings. This code style is enforced by our automated build pipeline. To make sure that IntelliJ and Android Studio are configured according to ktlint, run `./gradlew ktlintApplyToIdea`. To check that all code is properly formatted, run `./gradlew ktlintCheck`. See more details in [ktlint-gradle](https://github.com/jlleitschuh/ktlint-gradle). +For Kotlin, we follow [ktlint](https://ktlint.github.io/) style with default settings. This code style is enforced by our automated build pipeline. To make sure that IntelliJ and Android Studio are configured according to ktlint, run `./gradlew ktlintApplyToIdea`. To check that all code is properly formatted, run `./gradlew ktlintCheck`. You can install a Git pre-commit hook to ensure that the code is properly formatted when you commit using `./gradlew addKtlintFormatGitPreCommitHook`. See more details in [ktlint-gradle](https://github.com/jlleitschuh/ktlint-gradle). For legacy Java code, we don't have strict guidelines. Please follow a code style similar to the file you are modifying. Note that new classes should be written in Kotlin. Pull requests converting existing Java code to Kotlin are also welcome. diff --git a/uhabits-android/build.gradle b/uhabits-android/build.gradle index 4c84bc9de..7e71dc4d3 100644 --- a/uhabits-android/build.gradle +++ b/uhabits-android/build.gradle @@ -125,6 +125,7 @@ dependencies { testImplementation "junit:junit:4.12" testImplementation "org.mockito:mockito-core:2.28.2" testImplementation "org.mockito:mockito-inline:2.8.9" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" } kapt { diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsActivityTestComponent.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsActivityTestComponent.kt index 0ca7e26ac..9dade3a8c 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsActivityTestComponent.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitsActivityTestComponent.kt @@ -38,19 +38,13 @@ import org.mockito.Mockito.mock @Module class TestModule { - @Provides fun ListHabitsBehavior() = mock(ListHabitsBehavior::class.java) + @Provides fun listHabitsBehavior(): ListHabitsBehavior = mock(ListHabitsBehavior::class.java) } @ActivityScope @Component( - modules = arrayOf( - ActivityContextModule::class, - HabitsActivityModule::class, - ListHabitsModule::class, - HabitModule::class, - TestModule::class - ), - dependencies = arrayOf(HabitsApplicationComponent::class) + modules = [ActivityContextModule::class, HabitsActivityModule::class, ListHabitsModule::class, HabitModule::class, TestModule::class], + dependencies = [HabitsApplicationComponent::class] ) interface HabitsActivityTestComponent { fun getCheckmarkPanelViewFactory(): CheckmarkPanelViewFactory diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/AboutTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/AboutTest.java deleted file mode 100644 index 92f2971aa..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/AboutTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.acceptance; - -import androidx.test.filters.*; -import androidx.test.runner.*; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.isoron.uhabits.*; -import org.junit.*; -import org.junit.runner.*; - -import static org.isoron.uhabits.acceptance.steps.CommonSteps.*; -import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.*; -import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class AboutTest extends BaseUserInterfaceTest -{ - @Test - public void shouldDisplayAboutScreen() { - launchApp(); - clickMenu(ABOUT); - verifyDisplaysText("Loop Habit Tracker"); - verifyDisplaysText("Rate this app on Google Play"); - verifyDisplaysText("Developers"); - verifyDisplaysText("Translators"); - } - - @Test - public void shouldDisplayAboutScreenFromSettings() { - launchApp(); - clickMenu(SETTINGS); - clickText("About"); - verifyDisplaysText("Translators"); - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/AboutTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/AboutTest.kt new file mode 100644 index 000000000..ca54b609f --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/AboutTest.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016-2021 Á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.acceptance + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import org.isoron.uhabits.BaseUserInterfaceTest +import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp +import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplaysText +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu +import org.isoron.uhabits.acceptance.steps.WidgetSteps.clickText +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class AboutTest : BaseUserInterfaceTest() { + @Test + fun shouldDisplayAboutScreen() { + launchApp() + clickMenu(ListHabitsSteps.MenuItem.ABOUT) + verifyDisplaysText("Loop Habit Tracker") + verifyDisplaysText("Rate this app on Google Play") + verifyDisplaysText("Developers") + verifyDisplaysText("Translators") + } + + @Test + fun shouldDisplayAboutScreenFromSettings() { + launchApp() + clickMenu(ListHabitsSteps.MenuItem.SETTINGS) + clickText("About") + verifyDisplaysText("Translators") + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/BackupTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/BackupTest.kt index e657598ae..9da6375e7 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/BackupTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/BackupTest.kt @@ -27,6 +27,7 @@ import org.isoron.uhabits.acceptance.steps.CommonSteps.longClickText import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplaysText import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDoesNotDisplayText import org.isoron.uhabits.acceptance.steps.ListHabitsSteps +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu import org.isoron.uhabits.acceptance.steps.clearBackupFolder import org.isoron.uhabits.acceptance.steps.clearDownloadFolder import org.isoron.uhabits.acceptance.steps.copyBackupToDownloadFolder @@ -45,7 +46,7 @@ class BackupTest : BaseUserInterfaceTest() { copyBackupToDownloadFolder() longClickText("Wake up early") - ListHabitsSteps.clickMenu(ListHabitsSteps.MenuItem.DELETE) + clickMenu(ListHabitsSteps.MenuItem.DELETE) clickText("Yes") verifyDoesNotDisplayText("Wake up early") diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/HabitsTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/HabitsTest.java deleted file mode 100644 index a30a66d07..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/HabitsTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.acceptance; - -import androidx.test.filters.*; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.isoron.uhabits.*; -import org.junit.*; -import org.junit.runner.*; - -import static org.isoron.uhabits.acceptance.steps.CommonSteps.Screen.*; -import static org.isoron.uhabits.acceptance.steps.CommonSteps.*; -import static org.isoron.uhabits.acceptance.steps.EditHabitSteps.*; -import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.*; -import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class HabitsTest extends BaseUserInterfaceTest -{ - @Test - public void shouldCreateHabit() throws Exception { - shouldCreateHabit("this is a test description"); - } - - @Test - public void shouldCreateHabitBlankDescription() throws Exception { - shouldCreateHabit(""); - } - - private void shouldCreateHabit(String description) throws Exception - { - launchApp(); - - verifyShowsScreen(LIST_HABITS); - clickMenu(ADD); - - verifyShowsScreen(SELECT_HABIT_TYPE); - clickText("Yes or No"); - - verifyShowsScreen(EDIT_HABIT); - String testName = "Hello world"; - typeName(testName); - typeQuestion("Did you say hello to the world today?"); - typeDescription(description); - pickFrequency(); - pickColor(5); - clickSave(); - - verifyShowsScreen(LIST_HABITS); - verifyDisplaysText(testName); - } - - @Test - public void shouldShowHabitStatistics() throws Exception - { - launchApp(); - verifyShowsScreen(LIST_HABITS); - clickText("Track time"); - - verifyShowsScreen(SHOW_HABIT); - verifyDisplayGraphs(); - } - - @Test - public void shouldDeleteHabit() throws Exception - { - launchApp(); - - verifyShowsScreen(LIST_HABITS); - longClickText("Track time"); - clickMenu(DELETE); - clickText("Yes"); - verifyDoesNotDisplayText("Track time"); - } - - @Test - public void shouldEditHabit() throws Exception { - shouldEditHabit("this is a test description"); - } - - @Test - public void shouldEditHabitBlankDescription() throws Exception { - shouldEditHabit(""); - } - - private void shouldEditHabit(String description) throws Exception - { - launchApp(); - - verifyShowsScreen(LIST_HABITS); - longClickText("Track time"); - clickMenu(EDIT); - - verifyShowsScreen(EDIT_HABIT); - typeName("Take a walk"); - typeQuestion("Did you take a walk today?"); - typeDescription(description); - clickSave(); - - verifyShowsScreen(LIST_HABITS); - verifyDisplaysTextInSequence("Wake up early", "Take a walk", "Meditate"); - verifyDoesNotDisplayText("Track time"); - } - - @Test - public void shouldEditHabit_fromStatisticsScreen() throws Exception - { - launchApp(); - - verifyShowsScreen(LIST_HABITS); - clickText("Track time"); - - verifyShowsScreen(SHOW_HABIT); - clickMenu(EDIT); - - verifyShowsScreen(EDIT_HABIT); - typeName("Take a walk"); - typeQuestion("Did you take a walk today?"); - pickColor(10); - clickSave(); - - verifyShowsScreen(SHOW_HABIT); - verifyDisplaysText("Take a walk"); - pressBack(); - - verifyShowsScreen(LIST_HABITS); - verifyDisplaysText("Take a walk"); - verifyDoesNotDisplayText("Track time"); - } - - @Test - public void shouldArchiveAndUnarchiveHabits() throws Exception - { - launchApp(); - - verifyShowsScreen(LIST_HABITS); - longClickText("Track time"); - clickMenu(ARCHIVE); - verifyDoesNotDisplayText("Track time"); - clickMenu(TOGGLE_ARCHIVED); - verifyDisplaysText("Track time"); - - longClickText("Track time"); - clickMenu(UNARCHIVE); - clickMenu(TOGGLE_ARCHIVED); - verifyDisplaysText("Track time"); - } - - @Test - public void shouldToggleCheckmarksAndUpdateScore() throws Exception - { - launchApp(); - verifyShowsScreen(LIST_HABITS); - longPressCheckmarks("Wake up early", 2); - clickText("Wake up early"); - - verifyShowsScreen(SHOW_HABIT); - verifyDisplaysText("10%"); - } - - @Test - public void shouldHideCompleted() throws Exception - { - launchApp(); - verifyShowsScreen(LIST_HABITS); - verifyDisplaysText("Track time"); - verifyDisplaysText("Wake up early"); - - clickMenu(TOGGLE_COMPLETED); - verifyDoesNotDisplayText("Track time"); - verifyDisplaysText("Wake up early"); - - longPressCheckmarks("Wake up early", 1); - verifyDoesNotDisplayText("Wake up early"); - - clickMenu(TOGGLE_COMPLETED); - verifyDisplaysText("Track time"); - verifyDisplaysText("Wake up early"); - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/HabitsTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/HabitsTest.kt new file mode 100644 index 000000000..c022175b7 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/HabitsTest.kt @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016-2021 Á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.acceptance + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import org.isoron.uhabits.BaseUserInterfaceTest +import org.isoron.uhabits.acceptance.steps.CommonSteps +import org.isoron.uhabits.acceptance.steps.CommonSteps.clickText +import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp +import org.isoron.uhabits.acceptance.steps.CommonSteps.longClickText +import org.isoron.uhabits.acceptance.steps.CommonSteps.pressBack +import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplayGraphs +import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplaysText +import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplaysTextInSequence +import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDoesNotDisplayText +import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyShowsScreen +import org.isoron.uhabits.acceptance.steps.EditHabitSteps.clickSave +import org.isoron.uhabits.acceptance.steps.EditHabitSteps.pickColor +import org.isoron.uhabits.acceptance.steps.EditHabitSteps.pickFrequency +import org.isoron.uhabits.acceptance.steps.EditHabitSteps.typeDescription +import org.isoron.uhabits.acceptance.steps.EditHabitSteps.typeName +import org.isoron.uhabits.acceptance.steps.EditHabitSteps.typeQuestion +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.longPressCheckmarks +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class HabitsTest : BaseUserInterfaceTest() { + @Test + @Throws(Exception::class) + fun shouldCreateHabit() { + shouldCreateHabit("this is a test description") + } + + @Test + @Throws(Exception::class) + fun shouldCreateHabitBlankDescription() { + shouldCreateHabit("") + } + + @Throws(Exception::class) + private fun shouldCreateHabit(description: String) { + launchApp() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + clickMenu(ListHabitsSteps.MenuItem.ADD) + verifyShowsScreen(CommonSteps.Screen.SELECT_HABIT_TYPE) + clickText("Yes or No") + verifyShowsScreen(CommonSteps.Screen.EDIT_HABIT) + val testName = "Hello world" + typeName(testName) + typeQuestion("Did you say hello to the world today?") + typeDescription(description) + pickFrequency() + pickColor(5) + clickSave() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + verifyDisplaysText(testName) + } + + @Test + @Throws(Exception::class) + fun shouldShowHabitStatistics() { + launchApp() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + clickText("Track time") + verifyShowsScreen(CommonSteps.Screen.SHOW_HABIT) + verifyDisplayGraphs() + } + + @Test + @Throws(Exception::class) + fun shouldDeleteHabit() { + launchApp() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + longClickText("Track time") + clickMenu(ListHabitsSteps.MenuItem.DELETE) + clickText("Yes") + verifyDoesNotDisplayText("Track time") + } + + @Test + @Throws(Exception::class) + fun shouldEditHabit() { + shouldEditHabit("this is a test description") + } + + @Test + @Throws(Exception::class) + fun shouldEditHabitBlankDescription() { + shouldEditHabit("") + } + + @Throws(Exception::class) + private fun shouldEditHabit(description: String) { + launchApp() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + longClickText("Track time") + clickMenu(ListHabitsSteps.MenuItem.EDIT) + verifyShowsScreen(CommonSteps.Screen.EDIT_HABIT) + typeName("Take a walk") + typeQuestion("Did you take a walk today?") + typeDescription(description) + clickSave() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + verifyDisplaysTextInSequence("Wake up early", "Take a walk", "Meditate") + verifyDoesNotDisplayText("Track time") + } + + @Test + @Throws(Exception::class) + fun shouldEditHabit_fromStatisticsScreen() { + launchApp() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + clickText("Track time") + verifyShowsScreen(CommonSteps.Screen.SHOW_HABIT) + clickMenu(ListHabitsSteps.MenuItem.EDIT) + verifyShowsScreen(CommonSteps.Screen.EDIT_HABIT) + typeName("Take a walk") + typeQuestion("Did you take a walk today?") + pickColor(10) + clickSave() + verifyShowsScreen(CommonSteps.Screen.SHOW_HABIT) + verifyDisplaysText("Take a walk") + pressBack() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + verifyDisplaysText("Take a walk") + verifyDoesNotDisplayText("Track time") + } + + @Test + @Throws(Exception::class) + fun shouldArchiveAndUnarchiveHabits() { + launchApp() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + longClickText("Track time") + clickMenu(ListHabitsSteps.MenuItem.ARCHIVE) + verifyDoesNotDisplayText("Track time") + clickMenu(ListHabitsSteps.MenuItem.TOGGLE_ARCHIVED) + verifyDisplaysText("Track time") + longClickText("Track time") + clickMenu(ListHabitsSteps.MenuItem.UNARCHIVE) + clickMenu(ListHabitsSteps.MenuItem.TOGGLE_ARCHIVED) + verifyDisplaysText("Track time") + } + + @Test + @Throws(Exception::class) + fun shouldToggleCheckmarksAndUpdateScore() { + launchApp() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + longPressCheckmarks("Wake up early", 2) + clickText("Wake up early") + verifyShowsScreen(CommonSteps.Screen.SHOW_HABIT) + verifyDisplaysText("10%") + } + + @Test + @Throws(Exception::class) + fun shouldHideCompleted() { + launchApp() + verifyShowsScreen(CommonSteps.Screen.LIST_HABITS) + verifyDisplaysText("Track time") + verifyDisplaysText("Wake up early") + clickMenu(ListHabitsSteps.MenuItem.TOGGLE_COMPLETED) + verifyDoesNotDisplayText("Track time") + verifyDisplaysText("Wake up early") + longPressCheckmarks("Wake up early", 1) + verifyDoesNotDisplayText("Wake up early") + clickMenu(ListHabitsSteps.MenuItem.TOGGLE_COMPLETED) + verifyDisplaysText("Track time") + verifyDisplaysText("Wake up early") + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/LinksTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/LinksTest.java deleted file mode 100644 index 4af9c11b5..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/LinksTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.acceptance; - -import androidx.test.filters.*; -import androidx.test.runner.*; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.isoron.uhabits.*; -import org.junit.*; -import org.junit.runner.*; - -import static org.isoron.uhabits.acceptance.steps.CommonSteps.*; -import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.*; -import static org.isoron.uhabits.acceptance.steps.ListHabitsSteps.*; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class LinksTest extends BaseUserInterfaceTest -{ - @Test - public void shouldLinkToSourceCode() throws Exception - { - launchApp(); - clickMenu(ABOUT); - clickText("View source code at GitHub"); - verifyOpensWebsite("github.com"); - } - - @Test - public void shouldLinkToTranslationWebsite() throws Exception - { - launchApp(); - clickMenu(ABOUT); - clickText("Help translate this app"); - verifyOpensWebsite("translate.loophabits.org"); - } - - @Test - public void shouldLinkToHelp() throws Exception { - launchApp(); - clickMenu(HELP); - verifyOpensWebsite("github.com"); - } - - @Test - public void shouldLinkToHelpFromSettings() throws Exception { - launchApp(); - clickMenu(SETTINGS); - clickText("Help & FAQ"); - verifyOpensWebsite("github.com"); - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/LinksTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/LinksTest.kt new file mode 100644 index 000000000..b5e911519 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/LinksTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016-2021 Á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.acceptance + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import org.isoron.uhabits.BaseUserInterfaceTest +import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp +import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyOpensWebsite +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps +import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu +import org.isoron.uhabits.acceptance.steps.WidgetSteps.clickText +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@LargeTest +class LinksTest : BaseUserInterfaceTest() { + @Test + @Throws(Exception::class) + fun shouldLinkToSourceCode() { + launchApp() + clickMenu(ListHabitsSteps.MenuItem.ABOUT) + clickText("View source code at GitHub") + verifyOpensWebsite("github.com") + } + + @Test + @Throws(Exception::class) + fun shouldLinkToTranslationWebsite() { + launchApp() + clickMenu(ListHabitsSteps.MenuItem.ABOUT) + clickText("Help translate this app") + verifyOpensWebsite("translate.loophabits.org") + } + + @Test + @Throws(Exception::class) + fun shouldLinkToHelp() { + launchApp() + clickMenu(ListHabitsSteps.MenuItem.HELP) + verifyOpensWebsite("github.com") + } + + @Test + @Throws(Exception::class) + fun shouldLinkToHelpFromSettings() { + launchApp() + clickMenu(ListHabitsSteps.MenuItem.SETTINGS) + clickText("Help & FAQ") + verifyOpensWebsite("github.com") + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/WidgetTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/WidgetTest.java deleted file mode 100644 index 5a8f4d0a5..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/WidgetTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.acceptance; - -import androidx.test.filters.*; - -import org.isoron.uhabits.*; -import org.junit.*; - -import static org.isoron.uhabits.acceptance.steps.CommonSteps.*; -import static org.isoron.uhabits.acceptance.steps.WidgetSteps.*; -import static org.isoron.uhabits.acceptance.steps.WidgetSteps.clickText; - -@LargeTest -public class WidgetTest extends BaseUserInterfaceTest -{ - @Test - public void shouldCreateAndToggleCheckmarkWidget() throws Exception - { - dragCheckmarkWidgetToHomeScreen(); - Thread.sleep(3000); - clickText("Wake up early"); - clickText("Save"); - verifyCheckmarkWidgetIsShown(); - clickCheckmarkWidget(); - - launchApp(); - clickText("Wake up early"); - verifyDisplaysText("5%"); - - pressHome(); - clickCheckmarkWidget(); - - launchApp(); - clickText("Wake up early"); - verifyDisplaysText("0%"); - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/WidgetTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/WidgetTest.kt new file mode 100644 index 000000000..2ba898f29 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/WidgetTest.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016-2021 Á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.acceptance + +import androidx.test.filters.LargeTest +import org.isoron.uhabits.BaseUserInterfaceTest +import org.isoron.uhabits.acceptance.steps.CommonSteps.launchApp +import org.isoron.uhabits.acceptance.steps.CommonSteps.pressHome +import org.isoron.uhabits.acceptance.steps.CommonSteps.verifyDisplaysText +import org.isoron.uhabits.acceptance.steps.WidgetSteps.clickCheckmarkWidget +import org.isoron.uhabits.acceptance.steps.WidgetSteps.clickText +import org.isoron.uhabits.acceptance.steps.WidgetSteps.dragCheckmarkWidgetToHomeScreen +import org.isoron.uhabits.acceptance.steps.WidgetSteps.verifyCheckmarkWidgetIsShown +import org.junit.Test + +@LargeTest +class WidgetTest : BaseUserInterfaceTest() { + @Test + @Throws(Exception::class) + fun shouldCreateAndToggleCheckmarkWidget() { + dragCheckmarkWidgetToHomeScreen() + Thread.sleep(3000) + clickText("Wake up early") + clickText("Save") + verifyCheckmarkWidgetIsShown() + clickCheckmarkWidget() + launchApp() + clickText("Wake up early") + verifyDisplaysText("5%") + pressHome() + clickCheckmarkWidget() + launchApp() + clickText("Wake up early") + verifyDisplaysText("0%") + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/BackupSteps.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/BackupSteps.kt index 7fdc668d8..4e0a12ca2 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/BackupSteps.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/BackupSteps.kt @@ -20,8 +20,8 @@ package org.isoron.uhabits.acceptance.steps import androidx.test.uiautomator.UiSelector +import org.isoron.uhabits.BaseUserInterfaceTest.device import org.isoron.uhabits.acceptance.steps.CommonSteps.clickText -import org.isoron.uhabits.acceptance.steps.CommonSteps.device import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.MenuItem.SETTINGS import org.isoron.uhabits.acceptance.steps.ListHabitsSteps.clickMenu diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java deleted file mode 100644 index 82188510b..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.acceptance.steps; - -import android.view.*; - -import androidx.annotation.*; -import androidx.recyclerview.widget.*; -import androidx.test.espresso.*; -import androidx.test.espresso.contrib.*; -import androidx.test.uiautomator.*; - -import org.hamcrest.*; -import org.isoron.uhabits.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.activities.habits.list.*; - -import static android.os.Build.VERSION.*; -import static androidx.test.espresso.Espresso.*; -import static androidx.test.espresso.action.ViewActions.*; -import static androidx.test.espresso.assertion.PositionAssertions.*; -import static androidx.test.espresso.assertion.ViewAssertions.*; -import static androidx.test.espresso.matcher.ViewMatchers.*; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -public class CommonSteps extends BaseUserInterfaceTest -{ - public static void pressBack() - { - device.pressBack(); - } - - public static void clickText(String text) - { - scrollToText(text); - onView(withText(text)).perform(click()); - } - - public static void clickText(@StringRes int id) - { - onView(withText(id)).perform(click()); - } - - public static void launchApp() - { - startActivity(ListHabitsActivity.class); - assertTrue( - device.wait(Until.hasObject(By.pkg("org.isoron.uhabits")), 5000)); - device.waitForIdle(); - } - - public static void longClickText(String text) - { - scrollToText(text); - onView(withText(text)).perform(longClick()); - } - - public static void pressHome() - { - device.pressHome(); - device.waitForIdle(); - } - - public static void scrollToText(String text) - { - try - { - if (device - .findObject(new UiSelector().className(RecyclerView.class)) - .exists()) - { - onView(instanceOf(RecyclerView.class)).perform( - RecyclerViewActions.scrollTo( - hasDescendant(withText(text)))); - } - else - { - onView(withText(text)).perform(scrollTo()); - } - } - catch (PerformException e) - { - //ignored - } - } - - public static void verifyDisplayGraphs() - { - verifyDisplaysView("HistoryCard"); - verifyDisplaysView("ScoreCard"); - } - - public static void verifyDisplaysText(String text) - { - scrollToText(text); - onView(withText(text)).check(matches(isEnabled())); - } - - public static void verifyDisplaysTextInSequence(String... text) - { - verifyDisplaysText(text[0]); - for(int i = 1; i < text.length; i++) { - verifyDisplaysText(text[i]); - onView(withText(text[i])).check(isCompletelyBelow(withText(text[i-1]))); - } - } - - private static void verifyDisplaysView(String className) - { - onView(withClassName(endsWith(className))).check(matches(isEnabled())); - } - - public static void verifyDoesNotDisplayText(String text) - { - onView(withText(text)).check(doesNotExist()); - } - - public static void verifyOpensWebsite(String url) throws Exception - { - String browser_pkg = "org.chromium.webview_shell"; - if(SDK_INT <= 23) { - browser_pkg = "com.android.browser"; - } - assertTrue(device.wait(Until.hasObject(By.pkg(browser_pkg)), 5000)); - device.waitForIdle(); - assertTrue(device.findObject(new UiSelector().textContains(url)).exists()); - } - - public enum Screen - { - LIST_HABITS, SHOW_HABIT, EDIT_HABIT, SELECT_HABIT_TYPE - } - - public static void verifyShowsScreen(Screen screen) - { - switch(screen) - { - case LIST_HABITS: - onView(withClassName(endsWith("ListHabitsRootView"))) - .check(matches(isDisplayed())); - break; - - case SHOW_HABIT: - onView(withId(R.id.subtitleCard)).check(matches(isDisplayed())); - break; - - case EDIT_HABIT: - onView(withId(R.id.questionInput)).check(matches(isDisplayed())); - break; - - case SELECT_HABIT_TYPE: - onView(withText(R.string.yes_or_no_example)).check(matches(isDisplayed())); - break; - - default: - throw new IllegalStateException(); - } - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.kt new file mode 100644 index 000000000..4fa34d9cb --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/CommonSteps.kt @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016-2021 Á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.acceptance.steps + +import android.os.Build.VERSION +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso +import androidx.test.espresso.PerformException +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.PositionAssertions +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiSelector +import androidx.test.uiautomator.Until +import junit.framework.Assert.assertTrue +import org.hamcrest.CoreMatchers +import org.isoron.uhabits.BaseUserInterfaceTest +import org.isoron.uhabits.R +import org.isoron.uhabits.activities.habits.list.ListHabitsActivity + +object CommonSteps : BaseUserInterfaceTest() { + fun pressBack() { + device.pressBack() + } + + fun clickText(text: String?) { + scrollToText(text) + Espresso.onView(ViewMatchers.withText(text)).perform(ViewActions.click()) + } + + fun clickText(@StringRes id: Int) { + Espresso.onView(ViewMatchers.withText(id)).perform(ViewActions.click()) + } + + fun launchApp() { + startActivity(ListHabitsActivity::class.java) + assertTrue( + device.wait(Until.hasObject(By.pkg("org.isoron.uhabits")), 5000) + ) + device.waitForIdle() + } + + fun longClickText(text: String?) { + scrollToText(text) + Espresso.onView(ViewMatchers.withText(text)).perform(ViewActions.longClick()) + } + + fun pressHome() { + device.pressHome() + device.waitForIdle() + } + + fun scrollToText(text: String?) { + try { + if (device + .findObject(UiSelector().className(RecyclerView::class.java)) + .exists() + ) { + Espresso.onView(CoreMatchers.instanceOf(RecyclerView::class.java)).perform( + RecyclerViewActions.scrollTo( + ViewMatchers.hasDescendant(ViewMatchers.withText(text)) + ) + ) + } else { + Espresso.onView(ViewMatchers.withText(text)).perform(ViewActions.scrollTo()) + } + } catch (e: PerformException) { + // ignored + } + } + + fun verifyDisplayGraphs() { + verifyDisplaysView("HistoryCard") + verifyDisplaysView("ScoreCard") + } + + fun verifyDisplaysText(text: String?) { + scrollToText(text) + Espresso.onView(ViewMatchers.withText(text)) + .check(ViewAssertions.matches(ViewMatchers.isEnabled())) + } + + fun verifyDisplaysTextInSequence(vararg text: String?) { + verifyDisplaysText(text[0]) + for (i in 1 until text.size) { + verifyDisplaysText(text[i]) + Espresso.onView(ViewMatchers.withText(text[i])).check( + PositionAssertions.isCompletelyBelow( + ViewMatchers.withText( + text[i - 1] + ) + ) + ) + } + } + + private fun verifyDisplaysView(className: String) { + Espresso.onView(ViewMatchers.withClassName(CoreMatchers.endsWith(className))) + .check(ViewAssertions.matches(ViewMatchers.isEnabled())) + } + + fun verifyDoesNotDisplayText(text: String?) { + Espresso.onView(ViewMatchers.withText(text)).check(ViewAssertions.doesNotExist()) + } + + @Throws(Exception::class) + fun verifyOpensWebsite(url: String?) { + var browserPkg = "org.chromium.webview_shell" + if (VERSION.SDK_INT <= 23) { + browserPkg = "com.android.browser" + } + assertTrue(device.wait(Until.hasObject(By.pkg(browserPkg)), 5000)) + device.waitForIdle() + assertTrue(device.findObject(UiSelector().textContains(url)).exists()) + } + + fun verifyShowsScreen(screen: Screen?) { + when (screen) { + Screen.LIST_HABITS -> + Espresso.onView(ViewMatchers.withClassName(CoreMatchers.endsWith("ListHabitsRootView"))) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Screen.SHOW_HABIT -> + Espresso.onView(ViewMatchers.withId(R.id.subtitleCard)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Screen.EDIT_HABIT -> + Espresso.onView(ViewMatchers.withId(R.id.questionInput)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Screen.SELECT_HABIT_TYPE -> + Espresso.onView(ViewMatchers.withText(R.string.yes_or_no_example)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + else -> throw IllegalStateException() + } + } + + enum class Screen { + LIST_HABITS, SHOW_HABIT, EDIT_HABIT, SELECT_HABIT_TYPE + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.java deleted file mode 100644 index 3abbf1830..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.acceptance.steps; - -import androidx.test.uiautomator.*; - -import org.isoron.uhabits.*; - -import static androidx.test.espresso.Espresso.*; -import static androidx.test.espresso.action.ViewActions.*; -import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; -import static androidx.test.espresso.matcher.ViewMatchers.*; -import static org.isoron.uhabits.BaseUserInterfaceTest.*; - -public class EditHabitSteps -{ - public static void clickSave() - { - onView(withId(R.id.buttonSave)).perform(click()); - } - - public static void pickFrequency() - { - onView(withId(R.id.boolean_frequency_picker)).perform(click()); - onView(withText("SAVE")).perform(click()); - } - - public static void pickColor(int color) - { - onView(withId(R.id.colorButton)).perform(click()); - device.findObject(By.descStartsWith(String.format("Color %d", color))).click(); - } - - public static void typeName(String name) - { - typeTextWithId(R.id.nameInput, name); - } - - public static void typeQuestion(String name) - { - typeTextWithId(R.id.questionInput, name); - } - - public static void typeDescription(String description) - { - typeTextWithId(R.id.notesInput, description); - } - - public static void setReminder() - { - onView(withId(R.id.reminderTimePicker)).perform(click()); - onView(withId(R.id.done_button)).perform(click()); - } - - public static void clickReminderDays() - { - onView(withId(R.id.reminderDatePicker)).perform(click()); - } - - public static void unselectAllDays() - { - onView(withText("Saturday")).perform(click()); - onView(withText("Sunday")).perform(click()); - onView(withText("Monday")).perform(click()); - onView(withText("Tuesday")).perform(click()); - onView(withText("Wednesday")).perform(click()); - onView(withText("Thursday")).perform(click()); - onView(withText("Friday")).perform(click()); - } - - private static void typeTextWithId(int id, String name) - { - onView(withId(id)).perform(clearText(), typeText(name), closeSoftKeyboard()); - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.kt new file mode 100644 index 000000000..05dfc586e --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/EditHabitSteps.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016-2021 Á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.acceptance.steps + +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.uiautomator.By +import org.isoron.uhabits.BaseUserInterfaceTest +import org.isoron.uhabits.R + +object EditHabitSteps { + fun clickSave() { + Espresso.onView(ViewMatchers.withId(R.id.buttonSave)).perform(ViewActions.click()) + } + + fun pickFrequency() { + Espresso.onView(ViewMatchers.withId(R.id.boolean_frequency_picker)) + .perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withText("SAVE")).perform(ViewActions.click()) + } + + fun pickColor(color: Int) { + Espresso.onView(ViewMatchers.withId(R.id.colorButton)).perform(ViewActions.click()) + BaseUserInterfaceTest.device.findObject(By.descStartsWith(String.format("Color %d", color))) + .click() + } + + fun typeName(name: String) { + typeTextWithId(R.id.nameInput, name) + } + + fun typeQuestion(name: String) { + typeTextWithId(R.id.questionInput, name) + } + + fun typeDescription(description: String) { + typeTextWithId(R.id.notesInput, description) + } + + fun setReminder() { + Espresso.onView(ViewMatchers.withId(R.id.reminderTimePicker)).perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withId(R.id.done_button)).perform(ViewActions.click()) + } + + fun clickReminderDays() { + Espresso.onView(ViewMatchers.withId(R.id.reminderDatePicker)).perform(ViewActions.click()) + } + + fun unselectAllDays() { + Espresso.onView(ViewMatchers.withText("Saturday")).perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withText("Sunday")).perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withText("Monday")).perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withText("Tuesday")).perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withText("Wednesday")).perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withText("Thursday")).perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withText("Friday")).perform(ViewActions.click()) + } + + private fun typeTextWithId(id: Int, name: String) { + Espresso.onView(ViewMatchers.withId(id)).perform( + ViewActions.clearText(), + ViewActions.typeText(name), + ViewActions.closeSoftKeyboard() + ) + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.java deleted file mode 100644 index 73f0b2066..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.acceptance.steps; - -import androidx.test.espresso.*; -import android.view.*; - -import org.hamcrest.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.activities.habits.list.views.*; - -import java.util.*; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; -import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; -import static androidx.test.espresso.matcher.ViewMatchers.withClassName; -import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withParent; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static org.hamcrest.CoreMatchers.*; -import static org.isoron.uhabits.BaseUserInterfaceTest.device; -import static org.isoron.uhabits.acceptance.steps.CommonSteps.clickText; - -public abstract class ListHabitsSteps -{ - public static void clickMenu(MenuItem item) - { - switch (item) - { - case ABOUT: - clickTextInsideOverflowMenu(R.string.about); - break; - - case HELP: - clickTextInsideOverflowMenu(R.string.help); - break; - - case SETTINGS: - clickTextInsideOverflowMenu(R.string.settings); - break; - - case ADD: - clickViewWithId(R.id.actionCreateHabit); - break; - - case EDIT: - clickViewWithId(R.id.action_edit_habit); - break; - - case DELETE: - clickTextInsideOverflowMenu(R.string.delete); - break; - - case ARCHIVE: - clickTextInsideOverflowMenu(R.string.archive); - break; - - case UNARCHIVE: - clickTextInsideOverflowMenu(R.string.unarchive); - break; - - case TOGGLE_ARCHIVED: - clickViewWithId(R.id.action_filter); - clickText(R.string.hide_archived); - break; - - case TOGGLE_COMPLETED: - clickViewWithId(R.id.action_filter); - clickText(R.string.hide_completed); - break; - } - } - - private static void clickTextInsideOverflowMenu(int id) { - onView(allOf(withContentDescription("More options"), withParent(withParent(withClassName(endsWith("Toolbar")))))).perform(click()); - onView(withText(id)).perform(click()); - } - - private static void clickViewWithId(int id) - { - onView(withId(id)).perform(click()); - } - - private static ViewAction longClickDescendantWithClass(Class cls, int count) - { - return new ViewAction() - { - - @Override - public Matcher getConstraints() - { - return isEnabled(); - } - - @Override - public String getDescription() - { - return "perform on children"; - } - - @Override - public void perform(UiController uiController, View view) - { - LinkedList stack = new LinkedList<>(); - if (view instanceof ViewGroup) stack.push((ViewGroup) view); - int countRemaining = count; - - while (!stack.isEmpty()) - { - ViewGroup vg = stack.pop(); - for (int i = 0; i < vg.getChildCount(); i++) - { - View v = vg.getChildAt(i); - if (v instanceof ViewGroup) stack.push((ViewGroup) v); - if (cls.isInstance(v) && countRemaining > 0) - { - v.performLongClick(); - uiController.loopMainThreadUntilIdle(); - countRemaining--; - } - } - } - } - }; - } - - public static void longPressCheckmarks(String habit, int count) - { - CommonSteps.scrollToText(habit); - onView(allOf(hasDescendant(withText(habit)), - withClassName(endsWith("HabitCardView")))).perform( - longClickDescendantWithClass(CheckmarkButtonView.class, count)); - device.waitForIdle(); - } - - public enum MenuItem - { - ABOUT, HELP, SETTINGS, EDIT, DELETE, ARCHIVE, TOGGLE_ARCHIVED, - UNARCHIVE, TOGGLE_COMPLETED, ADD - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.kt new file mode 100644 index 000000000..ceaefb7c2 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/ListHabitsSteps.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016-2021 Á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.acceptance.steps + +import android.view.View +import android.view.ViewGroup +import androidx.test.espresso.Espresso +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.matcher.ViewMatchers +import org.hamcrest.CoreMatchers.allOf +import org.hamcrest.CoreMatchers.endsWith +import org.hamcrest.Matcher +import org.isoron.uhabits.BaseUserInterfaceTest +import org.isoron.uhabits.R +import org.isoron.uhabits.activities.habits.list.views.CheckmarkButtonView +import java.util.LinkedList + +object ListHabitsSteps { + fun clickMenu(item: MenuItem?) { + when (item) { + MenuItem.ABOUT -> clickTextInsideOverflowMenu(R.string.about) + MenuItem.HELP -> clickTextInsideOverflowMenu(R.string.help) + MenuItem.SETTINGS -> clickTextInsideOverflowMenu(R.string.settings) + MenuItem.ADD -> clickViewWithId(R.id.actionCreateHabit) + MenuItem.EDIT -> clickViewWithId(R.id.action_edit_habit) + MenuItem.DELETE -> clickTextInsideOverflowMenu(R.string.delete) + MenuItem.ARCHIVE -> clickTextInsideOverflowMenu(R.string.archive) + MenuItem.UNARCHIVE -> clickTextInsideOverflowMenu(R.string.unarchive) + MenuItem.TOGGLE_ARCHIVED -> { + clickViewWithId(R.id.action_filter) + CommonSteps.clickText(R.string.hide_archived) + } + MenuItem.TOGGLE_COMPLETED -> { + clickViewWithId(R.id.action_filter) + CommonSteps.clickText(R.string.hide_completed) + } + } + } + + private fun clickTextInsideOverflowMenu(id: Int) { + Espresso.onView( + allOf( + ViewMatchers.withContentDescription("More options"), + ViewMatchers.withParent( + ViewMatchers.withParent( + ViewMatchers.withClassName( + endsWith("Toolbar") + ) + ) + ) + ) + ).perform(ViewActions.click()) + Espresso.onView(ViewMatchers.withText(id)).perform(ViewActions.click()) + } + + private fun clickViewWithId(id: Int) { + Espresso.onView(ViewMatchers.withId(id)).perform(ViewActions.click()) + } + + private fun longClickDescendantWithClass(cls: Class<*>, count: Int): ViewAction { + return object : ViewAction { + override fun getConstraints(): Matcher { + return ViewMatchers.isEnabled() + } + + override fun getDescription(): String { + return "perform on children" + } + + override fun perform(uiController: UiController, view: View) { + val stack = LinkedList() + if (view is ViewGroup) stack.push(view) + var countRemaining = count + while (!stack.isEmpty()) { + val vg = stack.pop() + for (i in 0 until vg.childCount) { + val v = vg.getChildAt(i) + if (v is ViewGroup) stack.push(v) + if (cls.isInstance(v) && countRemaining > 0) { + v.performLongClick() + uiController.loopMainThreadUntilIdle() + countRemaining-- + } + } + } + } + } + } + + fun longPressCheckmarks(habit: String?, count: Int) { + CommonSteps.scrollToText(habit) + Espresso.onView( + allOf( + ViewMatchers.hasDescendant(ViewMatchers.withText(habit)), + ViewMatchers.withClassName(endsWith("HabitCardView")) + ) + ).perform( + longClickDescendantWithClass(CheckmarkButtonView::class.java, count) + ) + BaseUserInterfaceTest.device.waitForIdle() + } + + enum class MenuItem { + ABOUT, HELP, SETTINGS, EDIT, DELETE, ARCHIVE, TOGGLE_ARCHIVED, UNARCHIVE, TOGGLE_COMPLETED, ADD + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/WidgetSteps.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/WidgetSteps.java deleted file mode 100644 index caae50a0a..000000000 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/WidgetSteps.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.acceptance.steps; - -import androidx.test.uiautomator.*; - -import static android.os.Build.VERSION.SDK_INT; -import static org.junit.Assert.*; -import static org.isoron.uhabits.BaseUserInterfaceTest.*; - -public class WidgetSteps { - public static void clickCheckmarkWidget() throws Exception { - String view_id = "org.isoron.uhabits:id/imageView"; - device.findObject(new UiSelector().resourceId(view_id)).click(); - } - - public static void clickText(String s) throws Exception { - UiObject object = device.findObject(new UiSelector().text(s)); - if (!object.waitForExists(1000)) { - object = device.findObject(new UiSelector().text(s.toUpperCase())); - } - object.click(); - } - - public static void dragCheckmarkWidgetToHomeScreen() throws Exception { - openWidgetScreen(); - dragWidgetToHomeScreen(); - } - - private static void dragWidgetToHomeScreen() throws Exception { - int height = device.getDisplayHeight(); - int width = device.getDisplayWidth(); - device.findObject(new UiSelector().text("Checkmark")) - .dragTo(width / 2, height / 2, 40); - } - - private static void openWidgetScreen() throws Exception { - int h = device.getDisplayHeight(); - int w = device.getDisplayWidth(); - if (SDK_INT <= 21) { - device.pressHome(); - device.waitForIdle(); - device.findObject(new UiSelector().description("Apps")).click(); - device.findObject(new UiSelector().description("Apps")).click(); - device.findObject(new UiSelector().description("Widgets")).click(); - } else { - String list_id = "com.android.launcher3:id/widgets_list_view"; - device.pressHome(); - device.waitForIdle(); - device.drag(w / 2, h / 2, w / 2, h / 2, 8); - UiObject button = device.findObject(new UiSelector().text("WIDGETS")); - if(!button.waitForExists(1000)) { - button = device.findObject(new UiSelector().text("Widgets")); - } - button.click(); - if (SDK_INT >= 28) { - new UiScrollable(new UiSelector().resourceId(list_id)) - .scrollForward(); - } - new UiScrollable(new UiSelector().resourceId(list_id)) - .scrollIntoView(new UiSelector().text("Checkmark")); - } - } - - public static void verifyCheckmarkWidgetIsShown() throws Exception { - String view_id = "org.isoron.uhabits:id/imageView"; - assertTrue(device.findObject(new UiSelector().resourceId(view_id)).exists()); - assertFalse(device.findObject(new UiSelector().textStartsWith("Habit deleted")).exists()); - } -} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/WidgetSteps.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/WidgetSteps.kt new file mode 100644 index 000000000..2d3018221 --- /dev/null +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/steps/WidgetSteps.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016-2021 Á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.acceptance.steps + +import android.os.Build.VERSION +import androidx.test.uiautomator.UiScrollable +import androidx.test.uiautomator.UiSelector +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.isoron.uhabits.BaseUserInterfaceTest + +object WidgetSteps { + @Throws(Exception::class) + fun clickCheckmarkWidget() { + val viewId = "org.isoron.uhabits:id/imageView" + BaseUserInterfaceTest.device.findObject(UiSelector().resourceId(viewId)).click() + } + + @Throws(Exception::class) + fun clickText(s: String) { + var textObject = BaseUserInterfaceTest.device.findObject(UiSelector().text(s)) + if (!textObject.waitForExists(1000)) { + textObject = BaseUserInterfaceTest.device.findObject(UiSelector().text(s.toUpperCase())) + } + textObject.click() + } + + @Throws(Exception::class) + fun dragCheckmarkWidgetToHomeScreen() { + openWidgetScreen() + dragWidgetToHomeScreen() + } + + @Throws(Exception::class) + private fun dragWidgetToHomeScreen() { + val height = BaseUserInterfaceTest.device.displayHeight + val width = BaseUserInterfaceTest.device.displayWidth + BaseUserInterfaceTest.device.findObject(UiSelector().text("Checkmark")) + .dragTo(width / 2, height / 2, 40) + } + + @Throws(Exception::class) + private fun openWidgetScreen() { + val h = BaseUserInterfaceTest.device.displayHeight + val w = BaseUserInterfaceTest.device.displayWidth + if (VERSION.SDK_INT <= 21) { + BaseUserInterfaceTest.device.pressHome() + BaseUserInterfaceTest.device.waitForIdle() + BaseUserInterfaceTest.device.findObject(UiSelector().description("Apps")).click() + BaseUserInterfaceTest.device.findObject(UiSelector().description("Apps")).click() + BaseUserInterfaceTest.device.findObject(UiSelector().description("Widgets")).click() + } else { + val listId = "com.android.launcher3:id/widgets_list_view" + BaseUserInterfaceTest.device.pressHome() + BaseUserInterfaceTest.device.waitForIdle() + BaseUserInterfaceTest.device.drag(w / 2, h / 2, w / 2, h / 2, 8) + var button = BaseUserInterfaceTest.device.findObject(UiSelector().text("WIDGETS")) + if (!button.waitForExists(1000)) { + button = BaseUserInterfaceTest.device.findObject(UiSelector().text("Widgets")) + } + button.click() + if (VERSION.SDK_INT >= 28) { + UiScrollable(UiSelector().resourceId(listId)) + .scrollForward() + } + UiScrollable(UiSelector().resourceId(listId)) + .scrollIntoView(UiSelector().text("Checkmark")) + } + } + + @Throws(Exception::class) + fun verifyCheckmarkWidgetIsShown() { + val viewId = "org.isoron.uhabits:id/imageView" + assertTrue( + BaseUserInterfaceTest.device.findObject(UiSelector().resourceId(viewId)).exists() + ) + assertFalse( + BaseUserInterfaceTest.device.findObject(UiSelector().textStartsWith("Habit deleted")) + .exists() + ) + } +} diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EntryPanelViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EntryPanelViewTest.kt index b964c3654..669adb299 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EntryPanelViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EntryPanelViewTest.kt @@ -34,11 +34,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +private const val PATH = "habits/list/CheckmarkPanelView" + @RunWith(AndroidJUnit4::class) @MediumTest class EntryPanelViewTest : BaseViewTest() { - private val PATH = "habits/list/CheckmarkPanelView" private lateinit var view: CheckmarkPanelView @Before @@ -75,27 +76,6 @@ class EntryPanelViewTest : BaseViewTest() { assertRenders(view, "$PATH/render.png") } -// // Flaky test -// @Test -// fun testRender_withDifferentColor() { -// view.color = PaletteUtils.getAndroidTestColor(1) -// assertRenders(view, "$PATH/render_different_color.png") -// } - -// // Flaky test -// @Test -// fun testRender_Reversed() { -// prefs.isCheckmarkSequenceReversed = true -// assertRenders(view, "$PATH/render_reversed.png") -// } - -// // Flaky test -// @Test -// fun testRender_withOffset() { -// view.dataOffset = 3 -// assertRenders(view, "$PATH/render_offset.png") -// } - @Test fun testToggle() { val timestamps = mutableListOf() diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt index 2cf9a8034..20474090a 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.kt @@ -56,7 +56,7 @@ class HabitCardViewTest : BaseViewTest() { view = component.getHabitCardViewFactory().create().apply { habit = habit1 values = entries - score = habit1.scores.get(today).value + score = habit1.scores[today].value isSelected = false buttonCount = 5 } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt index 7b1b68e14..0fb7ad98f 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelViewTest.kt @@ -73,27 +73,6 @@ class NumberPanelViewTest : BaseViewTest() { assertRenders(view, "$PATH/render.png") } -// // Flaky test -// @Test -// fun testRender_withDifferentColor() { -// view.color = PaletteUtils.getAndroidTestColor(1) -// assertRenders(view, "$PATH/render_different_color.png") -// } - -// // Flaky test -// @Test -// fun testRender_Reversed() { -// prefs.isCheckmarkSequenceReversed = true -// assertRenders(view, "$PATH/render_reversed.png") -// } - -// // Flaky test -// @Test -// fun testRender_withOffset() { -// view.dataOffset = 3 -// assertRenders(view, "$PATH/render_offset.png") -// } - @Test fun testEdit() { val timestamps = mutableListOf() diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardViewTest.kt index 6b0f1cd18..63c700404 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/show/views/SubtitleCardViewTest.kt @@ -26,7 +26,7 @@ import org.isoron.uhabits.R import org.isoron.uhabits.core.models.Frequency import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.Reminder -import org.isoron.uhabits.core.models.WeekdayList.EVERY_DAY +import org.isoron.uhabits.core.models.WeekdayList.Companion.EVERY_DAY import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState import org.junit.Before import org.junit.Test diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/intents/IntentSchedulerTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/intents/IntentSchedulerTest.kt index c0991ba17..36a1926bd 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/intents/IntentSchedulerTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/intents/IntentSchedulerTest.kt @@ -103,9 +103,8 @@ class IntentSchedulerTest : BaseAndroidTest() { assertNull(ReminderReceiver.lastReceivedIntent) setSystemTime("America/Chicago", 2020, JUNE, 2, 22, 46) - val intent = ReminderReceiver.lastReceivedIntent - assertNotNull(intent) - assertThat(parseId(intent?.data!!), equalTo(habit.id)) + val intent = ReminderReceiver.lastReceivedIntent!! + assertThat(parseId(intent.data!!), equalTo(habit.id)) } @Test @@ -123,7 +122,6 @@ class IntentSchedulerTest : BaseAndroidTest() { assertNull(WidgetReceiver.lastReceivedIntent) setSystemTime("America/Chicago", 2020, JUNE, 2, 22, 46) - val intent = WidgetReceiver.lastReceivedIntent - assertNotNull(intent) + WidgetReceiver.lastReceivedIntent!! } } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java index 3ce819817..05a5e3265 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java @@ -68,13 +68,6 @@ public class CheckmarkWidgetViewTest extends BaseViewTest assertRenders(view, PATH + "checked.png"); } -// @Test -// public void testRender_implicitlyChecked() throws IOException -// { -// view.setCheckmarkValue(Checkmark.YES_AUTO); -// view.refresh(); -// assertRenders(view, PATH + "implicitly_checked.png"); -// } @Test public void testRender_largeSize() throws IOException @@ -83,11 +76,4 @@ public class CheckmarkWidgetViewTest extends BaseViewTest assertRenders(view, PATH + "large_size.png"); } -// @Test -// public void testRender_unchecked() throws IOException -// { -// view.setCheckmarkValue(Checkmark.NO); -// view.refresh(); -// assertRenders(view, PATH + "unchecked.png"); -// } } diff --git a/uhabits-android/src/main/java/com/android/datetimepicker/date/DatePickerDialog.java b/uhabits-android/src/main/java/com/android/datetimepicker/date/DatePickerDialog.java index 229b8df2e..4392fe496 100644 --- a/uhabits-android/src/main/java/com/android/datetimepicker/date/DatePickerDialog.java +++ b/uhabits-android/src/main/java/com/android/datetimepicker/date/DatePickerDialog.java @@ -114,7 +114,7 @@ public class DatePickerDialog extends DialogFragment implements public interface OnDateSetListener { /** - * @param view The view associated with this listener. + * @param dialog The dialog associated with this listener. * @param year The year that was set. * @param monthOfYear The month that was set (0-11) for compatibility * with {@link java.util.Calendar}. diff --git a/uhabits-android/src/main/java/com/android/datetimepicker/date/DayPickerView.java b/uhabits-android/src/main/java/com/android/datetimepicker/date/DayPickerView.java index 8d39e6f99..05348fe8c 100644 --- a/uhabits-android/src/main/java/com/android/datetimepicker/date/DayPickerView.java +++ b/uhabits-android/src/main/java/com/android/datetimepicker/date/DayPickerView.java @@ -167,7 +167,7 @@ public abstract class DayPickerView extends ListView implements OnScrollListener * the list will not be scrolled unless forceScroll is true. This time may * optionally be highlighted as selected as well. * - * @param time The time to move to + * @param day The day to move to * @param animate Whether to scroll to the given time or just redraw at the * new location * @param setSelected Whether to set the given time as selected diff --git a/uhabits-android/src/main/java/com/android/datetimepicker/time/RadialPickerLayout.java b/uhabits-android/src/main/java/com/android/datetimepicker/time/RadialPickerLayout.java index b877b8fdb..bb6be1300 100644 --- a/uhabits-android/src/main/java/com/android/datetimepicker/time/RadialPickerLayout.java +++ b/uhabits-android/src/main/java/com/android/datetimepicker/time/RadialPickerLayout.java @@ -392,7 +392,7 @@ public class RadialPickerLayout extends FrameLayout implements OnTouchListener { * Returns mapping of any input degrees (0 to 360) to one of 12 visible output degrees (all * multiples of 30), where the input will be "snapped" to the closest visible degrees. * @param degrees The input degrees - * @param forceAboveOrBelow The output may be forced to either the higher or lower step, or may + * @param forceHigherOrLower The output may be forced to either the higher or lower step, or may * be allowed to snap to whichever is closer. Use 1 to force strictly higher, -1 to force * strictly lower, and 0 to snap to the closer one. * @return output degrees, will be a multiple of 30 diff --git a/uhabits-android/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java b/uhabits-android/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java index 93c4d3ba1..0dc48ea6e 100644 --- a/uhabits-android/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java +++ b/uhabits-android/src/main/java/com/android/datetimepicker/time/TimePickerDialog.java @@ -720,7 +720,7 @@ public class TimePickerDialog extends AppCompatDialogFragment implements OnValue /** * Get out of keyboard mode. If there is nothing in typedTimes, revert to TimePicker's time. * - * @param changeDisplays If true, update the displays with the relevant time. + * @param updateDisplays If true, update the displays with the relevant time. */ private void finishKbMode(boolean updateDisplays) { diff --git a/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidDataView.kt b/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidDataView.kt index a36d02146..df5d5e184 100644 --- a/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidDataView.kt +++ b/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidDataView.kt @@ -25,6 +25,8 @@ import android.util.AttributeSet import android.view.GestureDetector import android.view.MotionEvent import android.widget.Scroller +import kotlin.math.abs +import kotlin.math.max /** * An AndroidView that implements scrolling. @@ -71,7 +73,7 @@ class AndroidDataView( dx: Float, dy: Float, ): Boolean { - if (Math.abs(dx) > Math.abs(dy)) { + if (abs(dx) > abs(dy)) { val parent = parent parent?.requestDisallowInterceptTouchEvent(true) } @@ -128,7 +130,7 @@ class AndroidDataView( view?.let { v -> var newDataOffset: Int = scroller.currX / (v.dataColumnWidth * canvas.innerDensity).toInt() - newDataOffset = Math.max(0, newDataOffset) + newDataOffset = max(0, newDataOffset) if (newDataOffset != v.dataOffset) { v.dataOffset = newDataOffset postInvalidate() diff --git a/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidImage.kt b/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidImage.kt index 54cd251fd..0b313f9a4 100644 --- a/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidImage.kt +++ b/uhabits-android/src/main/java/org/isoron/platform/gui/AndroidImage.kt @@ -42,7 +42,7 @@ class AndroidImage(private val bmp: Bitmap) : Image { } } -public fun Color.toInt(): Int { +fun Color.toInt(): Int { return android.graphics.Color.argb( (255 * this.alpha).roundToInt(), (255 * this.red).roundToInt(), diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/AndroidBugReporter.kt b/uhabits-android/src/main/java/org/isoron/uhabits/AndroidBugReporter.kt index fdc2b1a74..7dbee9c5b 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/AndroidBugReporter.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/AndroidBugReporter.kt @@ -68,7 +68,7 @@ open class AndroidBugReporter @Inject constructor(@AppContext private val contex if (log.size > maxLineCount) log.removeFirst() } for (l in log) { - builder.appendln(l) + builder.appendLine(l) } return builder.toString() } @@ -99,18 +99,18 @@ open class AndroidBugReporter @Inject constructor(@AppContext private val contex private fun getDeviceInfo(): String { val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager return buildString { - appendln("App Version Name: ${BuildConfig.VERSION_NAME}") - appendln("App Version Code: ${BuildConfig.VERSION_CODE}") - appendln("OS Version: ${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})") - appendln("OS API Level: ${Build.VERSION.SDK_INT}") - appendln("Device: ${Build.DEVICE}") - appendln("Model (Product): ${Build.MODEL} (${Build.PRODUCT})") - appendln("Manufacturer: ${Build.MANUFACTURER}") - appendln("Other tags: ${Build.TAGS}") - appendln("Screen Width: ${wm.defaultDisplay.width}") - appendln("Screen Height: ${wm.defaultDisplay.height}") - appendln("External storage state: ${Environment.getExternalStorageState()}") - appendln() + appendLine("App Version Name: ${BuildConfig.VERSION_NAME}") + appendLine("App Version Code: ${BuildConfig.VERSION_CODE}") + appendLine("OS Version: ${System.getProperty("os.version")} (${Build.VERSION.INCREMENTAL})") + appendLine("OS API Level: ${Build.VERSION.SDK_INT}") + appendLine("Device: ${Build.DEVICE}") + appendLine("Model (Product): ${Build.MODEL} (${Build.PRODUCT})") + appendLine("Manufacturer: ${Build.MANUFACTURER}") + appendLine("Other tags: ${Build.TAGS}") + appendLine("Screen Width: ${wm.defaultDisplay.width}") + appendLine("Screen Height: ${wm.defaultDisplay.height}") + appendLine("External storage state: ${Environment.getExternalStorageState()}") + appendLine() } } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.kt b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.kt index 8707309f0..37fa11420 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/HabitsApplication.kt @@ -83,7 +83,7 @@ class HabitsApplication : Application() { notificationTray.startListening() val prefs = component.preferences - prefs.setLastAppVersion(BuildConfig.VERSION_CODE) + prefs.lastAppVersion = BuildConfig.VERSION_CODE val taskRunner = component.taskRunner taskRunner.execute { @@ -106,11 +106,11 @@ class HabitsApplication : Application() { lateinit var component: HabitsApplicationComponent fun isTestMode(): Boolean { - try { + return try { Class.forName("org.isoron.uhabits.BaseAndroidTest") - return true + true } catch (e: ClassNotFoundException) { - return false + false } } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialog.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialog.kt similarity index 62% rename from uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialog.java rename to uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialog.kt index a36cac195..cf6248600 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialog.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialog.kt @@ -16,24 +16,20 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.activities.common.dialogs -package org.isoron.uhabits.activities.common.dialogs; - -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.ui.callbacks.*; -import org.isoron.uhabits.utils.*; +import com.android.colorpicker.ColorPickerDialog +import org.isoron.uhabits.core.ui.callbacks.OnColorPickedCallback +import org.isoron.uhabits.utils.toPaletteColor /** * Dialog that allows the user to choose a color. */ -public class ColorPickerDialog extends com.android.colorpicker.ColorPickerDialog -{ - public void setListener(OnColorPickedCallback callback) - { - super.setOnColorSelectedListener(c -> - { - PaletteColor pc = PaletteUtilsKt.toPaletteColor(c, getContext()); - callback.onColorPicked(pc); - }); +class ColorPickerDialog : ColorPickerDialog() { + fun setListener(callback: OnColorPickedCallback) { + super.setOnColorSelectedListener { c: Int -> + val pc = c.toPaletteColor(context!!) + callback.onColorPicked(pc) + } } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialogFactory.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialogFactory.java deleted file mode 100644 index d06272d63..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialogFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.activities.common.dialogs; - -import android.content.*; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.inject.*; -import org.isoron.uhabits.utils.*; - -import javax.inject.*; - -@ActivityScope -public class ColorPickerDialogFactory -{ - private final Context context; - - @Inject - public ColorPickerDialogFactory(@ActivityContext Context context) - { - this.context = context; - } - - public ColorPickerDialog create(PaletteColor color) - { - ColorPickerDialog dialog = new ColorPickerDialog(); - StyledResources res = new StyledResources(context); - int androidColor = PaletteUtilsKt.toThemedAndroidColor(color, context); - - dialog.initialize(R.string.color_picker_default_title, res.getPalette(), - androidColor, 4, com.android.colorpicker.ColorPickerDialog.SIZE_SMALL); - - return dialog; - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialogFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialogFactory.kt new file mode 100644 index 000000000..ac393d506 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ColorPickerDialogFactory.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016-2021 Á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.activities.common.dialogs + +import android.content.Context +import org.isoron.uhabits.R +import org.isoron.uhabits.core.models.PaletteColor +import org.isoron.uhabits.inject.ActivityContext +import org.isoron.uhabits.inject.ActivityScope +import org.isoron.uhabits.utils.StyledResources +import org.isoron.uhabits.utils.toThemedAndroidColor +import javax.inject.Inject + +@ActivityScope +class ColorPickerDialogFactory @Inject constructor(@param:ActivityContext private val context: Context) { + fun create(color: PaletteColor): ColorPickerDialog { + val dialog = ColorPickerDialog() + val res = StyledResources(context) + val androidColor = color.toThemedAndroidColor(context) + dialog.initialize( + R.string.color_picker_default_title, + res.getPalette(), + androidColor, + 4, + com.android.colorpicker.ColorPickerDialog.SIZE_SMALL + ) + return dialog + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmDeleteDialog.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmDeleteDialog.kt similarity index 52% rename from uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmDeleteDialog.java rename to uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmDeleteDialog.kt index 7309f27d5..bac6180b2 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmDeleteDialog.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmDeleteDialog.kt @@ -16,39 +16,34 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.activities.common.dialogs -package org.isoron.uhabits.activities.common.dialogs; - -import android.content.*; -import android.content.res.*; - -import androidx.annotation.*; -import androidx.appcompat.app.*; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.core.ui.callbacks.*; -import org.isoron.uhabits.inject.*; +import android.content.Context +import android.content.DialogInterface +import androidx.appcompat.app.AlertDialog +import org.isoron.uhabits.R +import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback +import org.isoron.uhabits.inject.ActivityContext /** * Dialog that asks the user confirmation before executing a delete operation. */ -public class ConfirmDeleteDialog extends AlertDialog -{ - public ConfirmDeleteDialog(@ActivityContext Context context, - @NonNull OnConfirmedCallback callback, - int quantity) - { - super(context); - Resources res = context.getResources(); - setTitle(res.getQuantityString(R.plurals.delete_habits_title, quantity)); - setMessage(res.getQuantityString(R.plurals.delete_habits_message, quantity)); - setButton(BUTTON_POSITIVE, - res.getString(R.string.yes), - (dialog, which) -> callback.onConfirmed() - ); - setButton(BUTTON_NEGATIVE, - res.getString(R.string.no), - (dialog, which) -> { } - ); +class ConfirmDeleteDialog( + @ActivityContext context: Context, + callback: OnConfirmedCallback, + quantity: Int +) : AlertDialog(context) { + init { + val res = context.resources + setTitle(res.getQuantityString(R.plurals.delete_habits_title, quantity)) + setMessage(res.getQuantityString(R.plurals.delete_habits_message, quantity)) + setButton( + BUTTON_POSITIVE, + res.getString(R.string.yes) + ) { dialog: DialogInterface?, which: Int -> callback.onConfirmed() } + setButton( + BUTTON_NEGATIVE, + res.getString(R.string.no) + ) { dialog: DialogInterface?, which: Int -> } } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmSyncKeyDialog.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmSyncKeyDialog.java deleted file mode 100644 index 337823e5f..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmSyncKeyDialog.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.activities.common.dialogs; - -import android.content.*; -import android.content.res.*; - -import androidx.annotation.*; -import androidx.appcompat.app.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.ui.callbacks.*; -import org.isoron.uhabits.inject.*; - -public class ConfirmSyncKeyDialog extends AlertDialog -{ - public ConfirmSyncKeyDialog(@ActivityContext Context context, - @NonNull OnConfirmedCallback callback) - { - super(context); - setTitle(R.string.device_sync); - Resources res = context.getResources(); - setMessage(res.getString(R.string.sync_confirm)); - setButton(BUTTON_POSITIVE, - res.getString(R.string.yes), - (dialog, which) -> callback.onConfirmed() - ); - setButton(BUTTON_NEGATIVE, - res.getString(R.string.no), - (dialog, which) -> { } - ); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmSyncKeyDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmSyncKeyDialog.kt new file mode 100644 index 000000000..b50ed50be --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/ConfirmSyncKeyDialog.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016-2021 Á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.activities.common.dialogs + +import android.content.Context +import android.content.DialogInterface +import androidx.appcompat.app.AlertDialog +import org.isoron.uhabits.R +import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback +import org.isoron.uhabits.inject.ActivityContext + +class ConfirmSyncKeyDialog( + @ActivityContext context: Context, + callback: OnConfirmedCallback +) : AlertDialog(context) { + init { + setTitle(R.string.device_sync) + val res = context.resources + setMessage(res.getString(R.string.sync_confirm)) + setButton( + BUTTON_POSITIVE, + res.getString(R.string.yes) + ) { dialog: DialogInterface?, which: Int -> callback.onConfirmed() } + setButton( + BUTTON_NEGATIVE, + res.getString(R.string.no) + ) { dialog: DialogInterface?, which: Int -> } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt index 470761f6a..c531e758b 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt @@ -34,6 +34,7 @@ import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.utils.InterfaceUtils import javax.inject.Inject +import kotlin.math.roundToLong class NumberPickerFactory @Inject constructor( @@ -52,7 +53,7 @@ class NumberPickerFactory val picker2 = view.findViewById(R.id.picker2) val tvUnit = view.findViewById(R.id.tvUnit) - val intValue = Math.round(value * 100).toInt() + val intValue = (value * 100).roundToLong().toInt() picker.minValue = 0 picker.maxValue = Integer.MAX_VALUE / 100 @@ -86,13 +87,12 @@ class NumberPickerFactory } InterfaceUtils.setupEditorAction( - picker, - TextView.OnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) - dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick() - false - } - ) + picker + ) { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) + dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick() + false + } return dialog } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.java deleted file mode 100644 index 4d40cc386..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.activities.common.dialogs; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatDialogFragment; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.core.models.WeekdayList; -import org.isoron.uhabits.core.utils.DateUtils; - -import java.util.Calendar; - -/** - * Dialog that allows the user to pick one or more days of the week. - */ -public class WeekdayPickerDialog extends AppCompatDialogFragment implements - DialogInterface.OnMultiChoiceClickListener, - DialogInterface.OnClickListener -{ - private static final String KEY_SELECTED_DAYS = "selectedDays"; - private boolean[] selectedDays; - - private OnWeekdaysPickedListener listener; - - @Override - public void onClick(DialogInterface dialog, int which, boolean isChecked) - { - selectedDays[which] = isChecked; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if(savedInstanceState != null){ - selectedDays = savedInstanceState.getBooleanArray(KEY_SELECTED_DAYS); - } - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBooleanArray(KEY_SELECTED_DAYS, selectedDays); - } - - @Override - public void onClick(DialogInterface dialog, int which) - { - if (listener != null) - listener.onWeekdaysSet(new WeekdayList(selectedDays)); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) - { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder - .setTitle(R.string.select_weekdays) - .setMultiChoiceItems(DateUtils.getLongWeekdayNames(Calendar.SATURDAY), - selectedDays, - this) - .setPositiveButton(android.R.string.yes, this) - .setNegativeButton(android.R.string.cancel, (dialog, which) -> { - dismiss(); - }); - - return builder.create(); - } - - public void setListener(OnWeekdaysPickedListener listener) - { - this.listener = listener; - } - - public void setSelectedDays(WeekdayList days) - { - this.selectedDays = days.toArray(); - } - - public interface OnWeekdaysPickedListener - { - void onWeekdaysSet(WeekdayList days); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.kt new file mode 100644 index 000000000..6e1f8cf28 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/WeekdayPickerDialog.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016-2021 Á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.activities.common.dialogs + +import android.app.Dialog +import android.content.DialogInterface +import android.content.DialogInterface.OnMultiChoiceClickListener +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatDialogFragment +import org.isoron.uhabits.R +import org.isoron.uhabits.core.models.WeekdayList +import org.isoron.uhabits.core.utils.DateUtils +import java.util.Calendar + +/** + * Dialog that allows the user to pick one or more days of the week. + */ +class WeekdayPickerDialog : + AppCompatDialogFragment(), + OnMultiChoiceClickListener, + DialogInterface.OnClickListener { + private var selectedDays: BooleanArray? = null + private var listener: OnWeekdaysPickedListener? = null + override fun onClick(dialog: DialogInterface, which: Int, isChecked: Boolean) { + selectedDays!![which] = isChecked + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (savedInstanceState != null) { + selectedDays = savedInstanceState.getBooleanArray(KEY_SELECTED_DAYS) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putBooleanArray(KEY_SELECTED_DAYS, selectedDays) + } + + override fun onClick(dialog: DialogInterface, which: Int) { + if (listener != null) listener!!.onWeekdaysSet(WeekdayList(selectedDays)) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val builder = AlertDialog.Builder( + activity!! + ) + builder + .setTitle(R.string.select_weekdays) + .setMultiChoiceItems( + DateUtils.getLongWeekdayNames(Calendar.SATURDAY), + selectedDays, + this + ) + .setPositiveButton(android.R.string.yes, this) + .setNegativeButton( + android.R.string.cancel + ) { _: DialogInterface?, _: Int -> dismiss() } + return builder.create() + } + + fun setListener(listener: OnWeekdaysPickedListener?) { + this.listener = listener + } + + fun setSelectedDays(days: WeekdayList) { + selectedDays = days.toArray() + } + + fun interface OnWeekdaysPickedListener { + fun onWeekdaysSet(days: WeekdayList) + } + + companion object { + private const val KEY_SELECTED_DAYS = "selectedDays" + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/TaskProgressBar.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/TaskProgressBar.kt index 1728033f1..aa1b74a97 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/TaskProgressBar.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/TaskProgressBar.kt @@ -56,8 +56,7 @@ class TaskProgressBar( fun update() { val callback = { - val activeTaskCount = runner.activeTaskCount - val newVisibility = when (activeTaskCount) { + val newVisibility = when (runner.activeTaskCount) { 0 -> GONE else -> VISIBLE } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt index 5e7aab70a..80adad0c7 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitActivity.kt @@ -197,7 +197,8 @@ class EditHabitActivity : AppCompatActivity() { binding.reminderDatePicker.setOnClickListener { val dialog = WeekdayPickerDialog() - dialog.setListener { days -> + + dialog.setListener { days: WeekdayList -> reminderDays = days if (reminderDays.isEmpty) reminderDays = WeekdayList.EVERY_DAY populateReminder() diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/HabitTypeDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/HabitTypeDialog.kt index 627247d3b..382dab282 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/HabitTypeDialog.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/edit/HabitTypeDialog.kt @@ -36,7 +36,7 @@ class HabitTypeDialog : AppCompatDialogFragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { val binding = SelectHabitTypeBinding.inflate(inflater, container, false) binding.buttonYesNo.setOnClickListener { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt index 3e7572934..81e01c82f 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsRootView.kt @@ -48,9 +48,9 @@ import org.isoron.uhabits.utils.dim import org.isoron.uhabits.utils.dp import org.isoron.uhabits.utils.setupToolbar import org.isoron.uhabits.utils.sres -import java.lang.Math.max -import java.lang.Math.min import javax.inject.Inject +import kotlin.math.max +import kotlin.math.min const val MAX_CHECKMARK_COUNT = 60 diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java deleted file mode 100644 index 38f1932f7..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.activities.habits.list.views; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import android.view.*; - -import androidx.recyclerview.widget.RecyclerView; - -import org.isoron.uhabits.activities.habits.list.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; -import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.inject.*; - -import java.util.*; - -import javax.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. - */ -@ActivityScope -public class HabitCardListAdapter - extends RecyclerView.Adapter implements - HabitCardListCache.Listener, - MidnightTimer.MidnightListener, - ListHabitsMenuBehavior.Adapter, - ListHabitsSelectionMenuBehavior.Adapter -{ - @NonNull - private ModelObservable observable; - - @Nullable - private HabitCardListView listView; - - @NonNull - private final LinkedList selected; - - @NonNull - private final HabitCardListCache cache; - - @NonNull - private Preferences preferences; - - private final MidnightTimer midnightTimer; - - @Inject - public HabitCardListAdapter(@NonNull HabitCardListCache cache, - @NonNull Preferences preferences, - @NonNull MidnightTimer midnightTimer) - { - this.preferences = preferences; - this.selected = new LinkedList<>(); - this.observable = new ModelObservable(); - this.cache = cache; - - this.midnightTimer = midnightTimer; - - cache.setListener(this); - cache.setCheckmarkCount( - ListHabitsRootViewKt.MAX_CHECKMARK_COUNT); - cache.setSecondaryOrder(preferences.getDefaultSecondaryOrder()); - cache.setPrimaryOrder(preferences.getDefaultPrimaryOrder()); - - setHasStableIds(true); - } - - @Override - public void atMidnight() - { - cache.refreshAllHabits(); - } - - public void cancelRefresh() - { - cache.cancelTasks(); - } - - /** - * Sets all items as not selected. - */ - @Override - public void clearSelection() - { - selected.clear(); - notifyDataSetChanged(); - observable.notifyListeners(); - } - - /** - * Returns the item that occupies a certain position on the list - * - * @param position position of the item - * @return the item at given position or null if position is invalid - */ - @Deprecated - @Nullable - public Habit getItem(int position) - { - return cache.getHabitByPosition(position); - } - - @Override - public int getItemCount() - { - return cache.getHabitCount(); - } - - @Override - public long getItemId(int position) - { - return getItem(position).getId(); - } - - @NonNull - public ModelObservable getObservable() - { - return observable; - } - - @Override - @NonNull - public List getSelected() - { - return new LinkedList<>(selected); - } - - /** - * Returns whether list of selected items is empty. - * - * @return true if selection is empty, false otherwise - */ - public boolean isSelectionEmpty() - { - return selected.isEmpty(); - } - - public boolean isSortable() - { - return cache.getPrimaryOrder() == HabitList.Order.BY_POSITION; - } - - /** - * Notify the adapter that it has been attached to a ListView. - */ - public void onAttached() - { - cache.onAttached(); - midnightTimer.addListener(this); - } - - @Override - public void onBindViewHolder(@Nullable HabitCardViewHolder holder, - int position) - { - if (holder == null) return; - if (listView == null) return; - - Habit habit = cache.getHabitByPosition(position); - double score = cache.getScore(habit.getId()); - int checkmarks[] = cache.getCheckmarks(habit.getId()); - boolean selected = this.selected.contains(habit); - - listView.bindCardView(holder, habit, score, checkmarks, selected); - } - - @Override - public void onViewAttachedToWindow(@Nullable HabitCardViewHolder holder) - { - if (listView == null) return; - listView.attachCardView(holder); - } - - @Override - public void onViewDetachedFromWindow(@Nullable HabitCardViewHolder holder) - { - if (listView == null) return; - listView.detachCardView(holder); - } - - @Override - public HabitCardViewHolder onCreateViewHolder(ViewGroup parent, - int viewType) - { - if (listView == null) return null; - View view = listView.createHabitCardView(); - return new HabitCardViewHolder(view); - } - - /** - * Notify the adapter that it has been detached from a ListView. - */ - public void onDetached() - { - cache.onDetached(); - midnightTimer.removeListener(this); - } - - @Override - public void onItemChanged(int position) - { - notifyItemChanged(position); - observable.notifyListeners(); - } - - @Override - public void onItemInserted(int position) - { - notifyItemInserted(position); - observable.notifyListeners(); - } - - @Override - public void onItemMoved(int fromPosition, int toPosition) - { - notifyItemMoved(fromPosition, toPosition); - observable.notifyListeners(); - } - - @Override - public void onItemRemoved(int position) - { - notifyItemRemoved(position); - observable.notifyListeners(); - } - - @Override - public void onRefreshFinished() - { - observable.notifyListeners(); - } - - /** - * Removes a list of habits from 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 habits list of habits to be removed - */ - @Override - public void performRemove(List habits) - { - for (Habit h : habits) - cache.remove(h.getId()); - } - - /** - * 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 performReorder(int from, int to) - { - cache.reorder(from, to); - } - - @Override - public void refresh() - { - cache.refreshAllHabits(); - } - - @Override - public void setFilter(HabitMatcher matcher) - { - cache.setFilter(matcher); - } - - /** - * 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; - } - - @Override - public void setPrimaryOrder(HabitList.Order order) - { - cache.setPrimaryOrder(order); - preferences.setDefaultPrimaryOrder(order); - } - - @Override - public void setSecondaryOrder(HabitList.Order order) { - cache.setSecondaryOrder(order); - preferences.setDefaultSecondaryOrder(order); - } - - @Override - public HabitList.Order getPrimaryOrder() - { - return cache.getPrimaryOrder(); - } - - /** - * 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); - if (h == null) return; - - int k = selected.indexOf(h); - if (k < 0) selected.add(h); - else selected.remove(h); - notifyDataSetChanged(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.kt new file mode 100644 index 000000000..8dae13f2d --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.kt @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2016-2021 Á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.activities.habits.list.views + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.isoron.uhabits.activities.habits.list.MAX_CHECKMARK_COUNT +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.HabitMatcher +import org.isoron.uhabits.core.models.ModelObservable +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.ui.screens.habits.list.HabitCardListCache +import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsMenuBehavior +import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsSelectionMenuBehavior +import org.isoron.uhabits.core.utils.MidnightTimer +import org.isoron.uhabits.inject.ActivityScope +import java.util.LinkedList +import javax.inject.Inject + +/** + * Provides data that backs a [HabitCardListView]. + * + * + * The data if fetched and cached by a [HabitCardListCache]. This adapter + * also holds a list of items that have been selected. + */ +@ActivityScope +class HabitCardListAdapter @Inject constructor( + private val cache: HabitCardListCache, + private val preferences: Preferences, + private val midnightTimer: MidnightTimer +) : RecyclerView.Adapter(), + HabitCardListCache.Listener, + MidnightTimer.MidnightListener, + ListHabitsMenuBehavior.Adapter, + ListHabitsSelectionMenuBehavior.Adapter { + val observable: ModelObservable = ModelObservable() + private var listView: HabitCardListView? = null + private val selected: LinkedList = LinkedList() + override fun atMidnight() { + cache.refreshAllHabits() + } + + fun cancelRefresh() { + cache.cancelTasks() + } + + /** + * Sets all items as not selected. + */ + override fun clearSelection() { + selected.clear() + notifyDataSetChanged() + observable.notifyListeners() + } + + /** + * Returns the item that occupies a certain position on the list + * + * @param position position of the item + * @return the item at given position or null if position is invalid + */ + @Deprecated("") + fun getItem(position: Int): Habit? { + return cache.getHabitByPosition(position) + } + + override fun getItemCount(): Int { + return cache.habitCount + } + + override fun getItemId(position: Int): Long { + return getItem(position)!!.id!! + } + + override fun getSelected(): List { + return LinkedList(selected) + } + + /** + * Returns whether list of selected items is empty. + * + * @return true if selection is empty, false otherwise + */ + val isSelectionEmpty: Boolean + get() = selected.isEmpty() + val isSortable: Boolean + get() = cache.primaryOrder == HabitList.Order.BY_POSITION + + /** + * Notify the adapter that it has been attached to a ListView. + */ + fun onAttached() { + cache.onAttached() + midnightTimer.addListener(this) + } + + override fun onBindViewHolder( + holder: HabitCardViewHolder, + position: Int + ) { + if (listView == null) return + val habit = cache.getHabitByPosition(position) + val score = cache.getScore(habit!!.id!!) + val checkmarks = cache.getCheckmarks(habit.id!!) + val selected = selected.contains(habit) + listView!!.bindCardView(holder, habit, score, checkmarks, selected) + } + + override fun onViewAttachedToWindow(holder: HabitCardViewHolder) { + listView!!.attachCardView(holder) + } + + override fun onViewDetachedFromWindow(holder: HabitCardViewHolder) { + listView!!.detachCardView(holder) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): HabitCardViewHolder { + val view = listView!!.createHabitCardView() + return HabitCardViewHolder(view) + } + + /** + * Notify the adapter that it has been detached from a ListView. + */ + fun onDetached() { + cache.onDetached() + midnightTimer.removeListener(this) + } + + override fun onItemChanged(position: Int) { + notifyItemChanged(position) + observable.notifyListeners() + } + + override fun onItemInserted(position: Int) { + notifyItemInserted(position) + observable.notifyListeners() + } + + override fun onItemMoved(fromPosition: Int, toPosition: Int) { + notifyItemMoved(fromPosition, toPosition) + observable.notifyListeners() + } + + override fun onItemRemoved(position: Int) { + notifyItemRemoved(position) + observable.notifyListeners() + } + + override fun onRefreshFinished() { + observable.notifyListeners() + } + + /** + * Removes a list of habits from 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 habits list of habits to be removed + */ + override fun performRemove(habits: List) { + for (habit in habits) cache.remove(habit.id!!) + } + + /** + * 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 + */ + fun performReorder(from: Int, to: Int) { + cache.reorder(from, to) + } + + override fun refresh() { + cache.refreshAllHabits() + } + + override fun setFilter(matcher: HabitMatcher) { + cache.setFilter(matcher) + } + + /** + * 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 + */ + fun setListView(listView: HabitCardListView?) { + this.listView = listView + } + + override fun setPrimaryOrder(order: HabitList.Order) { + cache.primaryOrder = order + preferences.defaultPrimaryOrder = order + } + + override fun setSecondaryOrder(order: HabitList.Order) { + cache.secondaryOrder = order + preferences.defaultSecondaryOrder = order + } + + override fun getPrimaryOrder(): HabitList.Order { + return cache.primaryOrder + } + + /** + * Selects or deselects the item at a given position. + * + * @param position position of the item to be toggled + */ + fun toggleSelection(position: Int) { + val h = getItem(position) ?: return + val k = selected.indexOf(h) + if (k < 0) selected.add(h) else selected.remove(h) + notifyDataSetChanged() + } + + init { + cache.setListener(this) + cache.setCheckmarkCount( + MAX_CHECKMARK_COUNT + ) + cache.secondaryOrder = preferences.defaultSecondaryOrder + cache.primaryOrder = preferences.defaultPrimaryOrder + setHasStableIds(true) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt index dceadbc10..ad2ebb270 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt @@ -39,12 +39,10 @@ class HabitCardListController @Inject constructor( private val selectionMenu: Lazy ) : HabitCardListView.Controller, ModelObservable.Listener { - private val NORMAL_MODE = NormalMode() - private val SELECTION_MODE = SelectionMode() private var activeMode: Mode init { - this.activeMode = NORMAL_MODE + this.activeMode = NormalMode() adapter.observable.addListener(this) } @@ -83,9 +81,9 @@ class HabitCardListController @Inject constructor( activeMode.startDrag(position) } - protected fun toggleSelection(position: Int) { + private fun toggleSelection(position: Int) { adapter.toggleSelection(position) - activeMode = if (adapter.isSelectionEmpty) NORMAL_MODE else SELECTION_MODE + activeMode = if (adapter.isSelectionEmpty) NormalMode() else SelectionMode() } private fun cancelSelection() { @@ -116,8 +114,7 @@ class HabitCardListController @Inject constructor( */ internal inner class NormalMode : Mode { override fun onItemClick(position: Int) { - val habit = adapter.getItem(position) - if (habit == null) return + val habit = adapter.getItem(position) ?: return behavior.onClickHabit(habit) } @@ -130,9 +127,9 @@ class HabitCardListController @Inject constructor( startSelection(position) } - protected fun startSelection(position: Int) { + private fun startSelection(position: Int) { toggleSelection(position) - activeMode = SELECTION_MODE + activeMode = SelectionMode() selectionMenu.get().onSelectionStart() } } @@ -158,8 +155,8 @@ class HabitCardListController @Inject constructor( notifyListener() } - protected fun notifyListener() { - if (activeMode === SELECTION_MODE) + private fun notifyListener() { + if (activeMode === SelectionMode()) selectionMenu.get().onSelectionChange() else selectionMenu.get().onSelectionFinish() diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt index 288e14305..3d8ad81c5 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.kt @@ -170,22 +170,22 @@ class HabitCardListView( inner class TouchHelperCallback : ItemTouchHelper.Callback() { override fun getMovementFlags( recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder + viewHolder: ViewHolder ): Int { return makeMovementFlags(UP or DOWN, START or END) } override fun onMove( recyclerView: RecyclerView, - from: RecyclerView.ViewHolder, - to: RecyclerView.ViewHolder + from: ViewHolder, + to: ViewHolder ): Boolean { controller.get().drop(from.adapterPosition, to.adapterPosition) return true } override fun onSwiped( - viewHolder: RecyclerView.ViewHolder, + viewHolder: ViewHolder, direction: Int ) { } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt index e8040db14..cb76becd4 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt @@ -59,8 +59,8 @@ class HabitCardViewFactory class HabitCardView( @ActivityContext context: Context, - private val checkmarkPanelFactory: CheckmarkPanelViewFactory, - private val numberPanelFactory: NumberPanelViewFactory, + checkmarkPanelFactory: CheckmarkPanelViewFactory, + numberPanelFactory: NumberPanelViewFactory, private val behavior: ListHabitsBehavior ) : FrameLayout(context), ModelObservable.Listener { @@ -174,7 +174,7 @@ class HabitCardView( } clipToPadding = false - layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) + layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT) val margin = dp(3f).toInt() setPadding(margin, 0, margin, margin) addView(innerFrame) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt index 379575652..491656a77 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.kt @@ -71,7 +71,7 @@ class NumberPanelView( setupButtons() } - override fun createButton() = buttonFactory.create()!! + override fun createButton() = buttonFactory.create() @Synchronized override fun setupButtons() { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCardView.kt index 997604e89..ddf657e27 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCardView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/OverviewCardView.kt @@ -27,6 +27,7 @@ import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState import org.isoron.uhabits.databinding.ShowHabitOverviewBinding import org.isoron.uhabits.utils.StyledResources import org.isoron.uhabits.utils.toThemedAndroidColor +import kotlin.math.abs class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { @@ -36,7 +37,7 @@ class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(con return String.format( "%s%.0f%%", if (percentageDiff >= 0) "+" else "\u2212", - Math.abs(percentageDiff) * 100 + abs(percentageDiff) * 100 ) } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsActivity.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsActivity.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java deleted file mode 100644 index a913c19af..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.activities.settings; - -import android.app.backup.*; -import android.content.*; -import android.net.*; -import android.os.*; -import android.provider.*; -import android.util.*; - -import androidx.annotation.*; -import androidx.preference.*; - -import org.isoron.uhabits.R; -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.intents.*; -import org.isoron.uhabits.notifications.*; -import org.isoron.uhabits.widgets.*; - -import java.util.*; - -import static android.media.RingtoneManager.*; -import static android.os.Build.VERSION.*; -import static org.isoron.uhabits.activities.habits.list.ListHabitsScreenKt.*; - -public class SettingsFragment extends PreferenceFragmentCompat - implements SharedPreferences.OnSharedPreferenceChangeListener -{ - private static int RINGTONE_REQUEST_CODE = 1; - - private SharedPreferences sharedPrefs; - - private RingtoneManager ringtoneManager; - - @NonNull - private Preferences prefs; - - @Nullable - private WidgetUpdater widgetUpdater; - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) - { - if (requestCode == RINGTONE_REQUEST_CODE) - { - ringtoneManager.update(data); - updateRingtoneDescription(); - return; - } - - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.preferences); - - Context appContext = getContext().getApplicationContext(); - if (appContext instanceof HabitsApplication) - { - HabitsApplication app = (HabitsApplication) appContext; - prefs = app.getComponent().getPreferences(); - widgetUpdater = app.getComponent().getWidgetUpdater(); - } - - setResultOnPreferenceClick("importData", RESULT_IMPORT_DATA); - setResultOnPreferenceClick("exportCSV", RESULT_EXPORT_CSV); - setResultOnPreferenceClick("exportDB", RESULT_EXPORT_DB); - setResultOnPreferenceClick("repairDB", RESULT_REPAIR_DB); - setResultOnPreferenceClick("bugReport", RESULT_BUG_REPORT); - - } - - @Override - public void onCreatePreferences(Bundle bundle, String s) - { - // NOP - } - - @Override - public void onPause() - { - sharedPrefs.unregisterOnSharedPreferenceChangeListener(this); - super.onPause(); - } - - @Override - public boolean onPreferenceTreeClick(Preference preference) - { - String key = preference.getKey(); - if (key == null) return false; - - if (key.equals("reminderSound")) - { - showRingtonePicker(); - return true; - } - else if (key.equals("reminderCustomize")) - { - if (SDK_INT < Build.VERSION_CODES.O) return true; - AndroidNotificationTray.Companion.createAndroidNotificationChannel(getContext()); - Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName()); - intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID); - startActivity(intent); - return true; - } - else if (key.equals("pref_sync_enabled_dummy")) - { - if (prefs.isSyncEnabled()) - { - prefs.disableSync(); - } - else - { - Context context = getActivity(); - context.startActivity(new IntentFactory().startSyncActivity(context)); - } - } - - return super.onPreferenceTreeClick(preference); - } - - @Override - public void onResume() - { - super.onResume(); - this.ringtoneManager = new RingtoneManager(getActivity()); - - sharedPrefs = getPreferenceManager().getSharedPreferences(); - sharedPrefs.registerOnSharedPreferenceChangeListener(this); - - if (!prefs.isDeveloper()) - { - PreferenceCategory devCategory = - (PreferenceCategory) findPreference("devCategory"); - devCategory.setVisible(false); - } - - updateWeekdayPreference(); - updateSyncPreferences(); - - // Temporarily disable this; we now always ask - findPreference("reminderSound").setVisible(false); - findPreference("pref_snooze_interval").setVisible(false); - } - - private void updateSyncPreferences() - { - findPreference("pref_sync_display").setVisible(prefs.isSyncEnabled()); - ((CheckBoxPreference) findPreference("pref_sync_enabled_dummy")).setChecked(prefs.isSyncEnabled()); - } - - private void updateWeekdayPreference() - { - ListPreference weekdayPref = (ListPreference) findPreference("pref_first_weekday"); - int currentFirstWeekday = prefs.getFirstWeekday().getDaysSinceSunday() + 1; - String[] dayNames = DateUtils.getLongWeekdayNames(Calendar.SATURDAY); - String[] dayValues = {"7", "1", "2", "3", "4", "5", "6"}; - weekdayPref.setEntries(dayNames); - weekdayPref.setEntryValues(dayValues); - weekdayPref.setDefaultValue(Integer.toString(currentFirstWeekday)); - weekdayPref.setSummary(dayNames[currentFirstWeekday % 7]); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) - { - if (key.equals("pref_widget_opacity") && widgetUpdater != null) - { - Log.d("SettingsFragment", "updating widgets"); - widgetUpdater.updateWidgets(); - } - - BackupManager.dataChanged("org.isoron.uhabits"); - updateWeekdayPreference(); - updateSyncPreferences(); - } - - private void setResultOnPreferenceClick(String key, final int result) - { - Preference pref = findPreference(key); - pref.setOnPreferenceClickListener(preference -> - { - getActivity().setResult(result); - getActivity().finish(); - return true; - }); - } - - private void showRingtonePicker() - { - Uri existingRingtoneUri = ringtoneManager.getURI(); - Uri defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI; - - Intent intent = new Intent(ACTION_RINGTONE_PICKER); - intent.putExtra(EXTRA_RINGTONE_TYPE, TYPE_NOTIFICATION); - intent.putExtra(EXTRA_RINGTONE_SHOW_DEFAULT, true); - intent.putExtra(EXTRA_RINGTONE_SHOW_SILENT, true); - intent.putExtra(EXTRA_RINGTONE_DEFAULT_URI, defaultRingtoneUri); - intent.putExtra(EXTRA_RINGTONE_EXISTING_URI, existingRingtoneUri); - startActivityForResult(intent, RINGTONE_REQUEST_CODE); - } - - private void updateRingtoneDescription() - { - String ringtoneName = ringtoneManager.getName(); - if (ringtoneName == null) return; - Preference ringtonePreference = findPreference("reminderSound"); - ringtonePreference.setSummary(ringtoneName); - } -} \ No newline at end of file diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.kt new file mode 100644 index 000000000..38418e1b5 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsFragment.kt @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2016-2021 Á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.activities.settings + +import android.app.backup.BackupManager +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.os.Build +import android.os.Build.VERSION +import android.os.Bundle +import android.provider.Settings +import android.util.Log +import androidx.preference.CheckBoxPreference +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.R +import org.isoron.uhabits.activities.habits.list.RESULT_BUG_REPORT +import org.isoron.uhabits.activities.habits.list.RESULT_EXPORT_CSV +import org.isoron.uhabits.activities.habits.list.RESULT_EXPORT_DB +import org.isoron.uhabits.activities.habits.list.RESULT_IMPORT_DATA +import org.isoron.uhabits.activities.habits.list.RESULT_REPAIR_DB +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.ui.NotificationTray +import org.isoron.uhabits.core.utils.DateUtils.Companion.getLongWeekdayNames +import org.isoron.uhabits.intents.IntentFactory +import org.isoron.uhabits.notifications.AndroidNotificationTray.Companion.createAndroidNotificationChannel +import org.isoron.uhabits.notifications.RingtoneManager +import org.isoron.uhabits.widgets.WidgetUpdater +import java.util.Calendar + +class SettingsFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeListener { + private var sharedPrefs: SharedPreferences? = null + private var ringtoneManager: RingtoneManager? = null + private lateinit var prefs: Preferences + private var widgetUpdater: WidgetUpdater? = null + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == RINGTONE_REQUEST_CODE) { + ringtoneManager!!.update(data) + updateRingtoneDescription() + return + } + super.onActivityResult(requestCode, resultCode, data) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + addPreferencesFromResource(R.xml.preferences) + val appContext = context!!.applicationContext + if (appContext is HabitsApplication) { + prefs = appContext.component.preferences + widgetUpdater = appContext.component.widgetUpdater + } + setResultOnPreferenceClick("importData", RESULT_IMPORT_DATA) + setResultOnPreferenceClick("exportCSV", RESULT_EXPORT_CSV) + setResultOnPreferenceClick("exportDB", RESULT_EXPORT_DB) + setResultOnPreferenceClick("repairDB", RESULT_REPAIR_DB) + setResultOnPreferenceClick("bugReport", RESULT_BUG_REPORT) + } + + override fun onCreatePreferences(bundle: Bundle, s: String) { + // NOP + } + + override fun onPause() { + sharedPrefs!!.unregisterOnSharedPreferenceChangeListener(this) + super.onPause() + } + + override fun onPreferenceTreeClick(preference: Preference): Boolean { + val key = preference.key ?: return false + if (key == "reminderSound") { + showRingtonePicker() + return true + } else if (key == "reminderCustomize") { + if (VERSION.SDK_INT < Build.VERSION_CODES.O) return true + createAndroidNotificationChannel(context!!) + val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) + intent.putExtra(Settings.EXTRA_APP_PACKAGE, context!!.packageName) + intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationTray.REMINDERS_CHANNEL_ID) + startActivity(intent) + return true + } else if (key == "pref_sync_enabled_dummy") { + if (prefs.isSyncEnabled) { + prefs.disableSync() + } else { + val context: Context? = activity + context!!.startActivity(IntentFactory().startSyncActivity(context)) + } + } + return super.onPreferenceTreeClick(preference) + } + + override fun onResume() { + super.onResume() + ringtoneManager = RingtoneManager(activity!!) + sharedPrefs = preferenceManager.sharedPreferences + sharedPrefs!!.registerOnSharedPreferenceChangeListener(this) + if (!prefs.isDeveloper) { + val devCategory = findPreference("devCategory") as PreferenceCategory + devCategory.isVisible = false + } + updateWeekdayPreference() + updateSyncPreferences() + + // Temporarily disable this; we now always ask + findPreference("reminderSound").isVisible = false + findPreference("pref_snooze_interval").isVisible = false + } + + private fun updateSyncPreferences() { + findPreference("pref_sync_display").isVisible = prefs.isSyncEnabled + (findPreference("pref_sync_enabled_dummy") as CheckBoxPreference).isChecked = + prefs.isSyncEnabled + } + + private fun updateWeekdayPreference() { + val weekdayPref = findPreference("pref_first_weekday") as ListPreference + val currentFirstWeekday = prefs.firstWeekday.daysSinceSunday + 1 + val dayNames = getLongWeekdayNames(Calendar.SATURDAY) + val dayValues = arrayOf("7", "1", "2", "3", "4", "5", "6") + weekdayPref.entries = dayNames + weekdayPref.entryValues = dayValues + weekdayPref.setDefaultValue(currentFirstWeekday.toString()) + weekdayPref.summary = dayNames[currentFirstWeekday % 7] + } + + override fun onSharedPreferenceChanged( + sharedPreferences: SharedPreferences, + key: String + ) { + if (key == "pref_widget_opacity" && widgetUpdater != null) { + Log.d("SettingsFragment", "updating widgets") + widgetUpdater!!.updateWidgets() + } + BackupManager.dataChanged("org.isoron.uhabits") + updateWeekdayPreference() + updateSyncPreferences() + } + + private fun setResultOnPreferenceClick(key: String, result: Int) { + val pref = findPreference(key) + pref.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + activity!!.setResult(result) + activity!!.finish() + true + } + } + + private fun showRingtonePicker() { + val existingRingtoneUri = ringtoneManager!!.getURI() + val defaultRingtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI + val intent = Intent(android.media.RingtoneManager.ACTION_RINGTONE_PICKER) + intent.putExtra( + android.media.RingtoneManager.EXTRA_RINGTONE_TYPE, + android.media.RingtoneManager.TYPE_NOTIFICATION + ) + intent.putExtra(android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true) + intent.putExtra(android.media.RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true) + intent.putExtra( + android.media.RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, + defaultRingtoneUri + ) + intent.putExtra( + android.media.RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, + existingRingtoneUri + ) + startActivityForResult(intent, RINGTONE_REQUEST_CODE) + } + + private fun updateRingtoneDescription() { + val ringtoneName = ringtoneManager!!.getName() ?: return + val ringtonePreference = findPreference("reminderSound") + ringtonePreference.summary = ringtoneName + } + + companion object { + private const val RINGTONE_REQUEST_CODE = 1 + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/sync/SyncActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/sync/SyncActivity.kt index 89bf8ef52..5b84ef8c2 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/sync/SyncActivity.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/sync/SyncActivity.kt @@ -68,7 +68,7 @@ class SyncActivity : AppCompatActivity(), SyncBehavior.Screen { title = resources.getString(R.string.device_sync), ) binding.syncLink.setOnClickListener { copyToClipboard() } - binding.instructions.setText(Html.fromHtml(resources.getString(R.string.sync_instructions))) + binding.instructions.text = Html.fromHtml(resources.getString(R.string.sync_instructions)) setContentView(binding.root) } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidCursor.kt b/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidCursor.kt index 683b6368b..cb0b9f894 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidCursor.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidCursor.kt @@ -27,22 +27,22 @@ class AndroidCursor(private val cursor: android.database.Cursor) : Cursor { override fun moveToNext() = cursor.moveToNext() override fun getInt(index: Int): Int? { - if (cursor.isNull(index)) return null - else return cursor.getInt(index) + return if (cursor.isNull(index)) null + else cursor.getInt(index) } override fun getLong(index: Int): Long? { - if (cursor.isNull(index)) return null - else return cursor.getLong(index) + return if (cursor.isNull(index)) null + else cursor.getLong(index) } override fun getDouble(index: Int): Double? { - if (cursor.isNull(index)) return null - else return cursor.getDouble(index) + return if (cursor.isNull(index)) null + else cursor.getDouble(index) } override fun getString(index: Int): String? { - if (cursor.isNull(index)) return null - else return cursor.getString(index) + return if (cursor.isNull(index)) null + else cursor.getString(index) } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidDatabase.kt b/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidDatabase.kt index c225b7dac..d764e7a63 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidDatabase.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidDatabase.kt @@ -51,7 +51,7 @@ class AndroidDatabase( return db.update(tableName, contValues, where, params) } - override fun insert(tableName: String, values: Map): Long? { + override fun insert(tableName: String, values: Map): Long { val contValues = mapToContentValues(values) return db.insert(tableName, null, contValues) } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/inject/AppContextModule.java b/uhabits-android/src/main/java/org/isoron/uhabits/inject/ActivityContextModule.kt similarity index 67% rename from uhabits-android/src/main/java/org/isoron/uhabits/inject/AppContextModule.java rename to uhabits-android/src/main/java/org/isoron/uhabits/inject/ActivityContextModule.kt index 307cac9af..89efc00af 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/inject/AppContextModule.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/inject/ActivityContextModule.kt @@ -16,28 +16,14 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.inject -package org.isoron.uhabits.inject; - -import android.content.Context; - -import dagger.Module; -import dagger.Provides; +import android.content.Context +import dagger.Module +import dagger.Provides @Module -public class AppContextModule -{ - private final Context context; - - public AppContextModule(@AppContext Context context) - { - this.context = context; - } - - @Provides - @AppContext - Context getContext() - { - return context; - } -} +class ActivityContextModule( + @get:Provides + @get:ActivityContext val context: Context +) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/inject/ActivityContextModule.java b/uhabits-android/src/main/java/org/isoron/uhabits/inject/AppContextModule.kt similarity index 67% rename from uhabits-android/src/main/java/org/isoron/uhabits/inject/ActivityContextModule.java rename to uhabits-android/src/main/java/org/isoron/uhabits/inject/AppContextModule.kt index 919a0622d..74b433ea2 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/inject/ActivityContextModule.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/inject/AppContextModule.kt @@ -16,29 +16,15 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.inject -package org.isoron.uhabits.inject; - - -import android.content.Context; - -import dagger.Module; -import dagger.Provides; +import android.content.Context +import dagger.Module +import dagger.Provides @Module -public class ActivityContextModule -{ - private Context context; - - public ActivityContextModule(Context context) - { - this.context = context; - } - - @Provides - @ActivityContext - public Context getContext() - { - return context; - } -} +class AppContextModule( + @get:Provides + @get:AppContext + @param:AppContext val context: Context +) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsActivityComponent.kt b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsActivityComponent.kt index eba75600c..3c16ef030 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsActivityComponent.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsActivityComponent.kt @@ -32,13 +32,8 @@ import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior @ActivityScope @Component( - modules = arrayOf( - ActivityContextModule::class, - HabitsActivityModule::class, - ListHabitsModule::class, - HabitModule::class - ), - dependencies = arrayOf(HabitsApplicationComponent::class) + modules = [ActivityContextModule::class, HabitsActivityModule::class, ListHabitsModule::class, HabitModule::class], + dependencies = [HabitsApplicationComponent::class] ) interface HabitsActivityComponent { val colorPickerDialogFactory: ColorPickerDialogFactory diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.java b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.java deleted file mode 100644 index dd4d27504..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.inject; - -import android.content.*; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.io.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.reminders.*; -import org.isoron.uhabits.core.sync.*; -import org.isoron.uhabits.core.tasks.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.ui.screens.habits.list.*; -import org.isoron.uhabits.core.utils.*; -import org.isoron.uhabits.intents.*; -import org.isoron.uhabits.receivers.*; -import org.isoron.uhabits.tasks.*; -import org.isoron.uhabits.widgets.*; - -import dagger.*; - -@AppScope -@Component(modules = { - AppContextModule.class, - HabitsModule.class, - AndroidTaskRunner.class, -}) -public interface HabitsApplicationComponent -{ - CommandRunner getCommandRunner(); - - @AppContext - Context getContext(); - - GenericImporter getGenericImporter(); - - HabitCardListCache getHabitCardListCache(); - - HabitList getHabitList(); - - IntentFactory getIntentFactory(); - - IntentParser getIntentParser(); - - Logging getLogging(); - - MidnightTimer getMidnightTimer(); - - ModelFactory getModelFactory(); - - NotificationTray getNotificationTray(); - - PendingIntentFactory getPendingIntentFactory(); - - Preferences getPreferences(); - - ReminderScheduler getReminderScheduler(); - - ReminderController getReminderController(); - - TaskRunner getTaskRunner(); - - WidgetPreferences getWidgetPreferences(); - - WidgetUpdater getWidgetUpdater(); - - SyncManager getSyncManager(); -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.kt b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.kt new file mode 100644 index 000000000..49bb8d67f --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/inject/HabitsApplicationComponent.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016-2021 Á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.inject + +import android.content.Context +import dagger.Component +import org.isoron.uhabits.core.AppScope +import org.isoron.uhabits.core.commands.CommandRunner +import org.isoron.uhabits.core.io.GenericImporter +import org.isoron.uhabits.core.io.Logging +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.ModelFactory +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.preferences.WidgetPreferences +import org.isoron.uhabits.core.reminders.ReminderScheduler +import org.isoron.uhabits.core.sync.SyncManager +import org.isoron.uhabits.core.tasks.TaskRunner +import org.isoron.uhabits.core.ui.NotificationTray +import org.isoron.uhabits.core.ui.screens.habits.list.HabitCardListCache +import org.isoron.uhabits.core.utils.MidnightTimer +import org.isoron.uhabits.intents.IntentFactory +import org.isoron.uhabits.intents.IntentParser +import org.isoron.uhabits.intents.PendingIntentFactory +import org.isoron.uhabits.receivers.ReminderController +import org.isoron.uhabits.tasks.AndroidTaskRunner +import org.isoron.uhabits.widgets.WidgetUpdater + +@AppScope +@Component(modules = [AppContextModule::class, HabitsModule::class, AndroidTaskRunner::class]) +interface HabitsApplicationComponent { + val commandRunner: CommandRunner + + @get:AppContext + val context: Context + val genericImporter: GenericImporter + val habitCardListCache: HabitCardListCache + val habitList: HabitList + val intentFactory: IntentFactory + val intentParser: IntentParser + val logging: Logging + val midnightTimer: MidnightTimer + val modelFactory: ModelFactory + val notificationTray: NotificationTray + val pendingIntentFactory: PendingIntentFactory + val preferences: Preferences + val reminderScheduler: ReminderScheduler + val reminderController: ReminderController + val taskRunner: TaskRunner + val widgetPreferences: WidgetPreferences + val widgetUpdater: WidgetUpdater + val syncManager: SyncManager +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentParser.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentParser.kt index 297ed027c..6be35b4c9 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentParser.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentParser.kt @@ -44,9 +44,8 @@ class IntentParser } private fun parseHabit(uri: Uri): Habit { - val habit = habits.getById(parseId(uri)) + return habits.getById(parseId(uri)) ?: throw IllegalArgumentException("habit not found") - return habit } private fun parseTimestamp(intent: Intent): Timestamp { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.kt index 6964f3363..5a4c79f1d 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentScheduler.kt @@ -36,6 +36,7 @@ import org.isoron.uhabits.core.utils.DateFormats import org.isoron.uhabits.inject.AppContext import java.util.Date import javax.inject.Inject +import kotlin.math.min @AppScope class IntentScheduler @@ -84,7 +85,7 @@ class IntentScheduler } private fun logReminderScheduled(habit: Habit, reminderTime: Long) { - val min = Math.min(5, habit.name.length) + val min = min(5, habit.name.length) val name = habit.name.substring(0, min) val df = DateFormats.getBackupDateFormat() val time = df.format(Date(reminderTime)) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt index 1917ad807..0a195d9c0 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt @@ -41,7 +41,7 @@ class PendingIntentFactory ) { fun addCheckmark(habit: Habit, timestamp: Timestamp?): PendingIntent = - PendingIntent.getBroadcast( + getBroadcast( context, 1, Intent(context, WidgetReceiver::class.java).apply { @@ -53,7 +53,7 @@ class PendingIntentFactory ) fun dismissNotification(habit: Habit): PendingIntent = - PendingIntent.getBroadcast( + getBroadcast( context, 0, Intent(context, ReminderReceiver::class.java).apply { @@ -64,7 +64,7 @@ class PendingIntentFactory ) fun removeRepetition(habit: Habit): PendingIntent = - PendingIntent.getBroadcast( + getBroadcast( context, 3, Intent(context, WidgetReceiver::class.java).apply { @@ -90,7 +90,7 @@ class PendingIntentFactory reminderTime: Long?, timestamp: Long ): PendingIntent = - PendingIntent.getBroadcast( + getBroadcast( context, (habit.id!! % Integer.MAX_VALUE).toInt() + 1, Intent(context, ReminderReceiver::class.java).apply { @@ -103,7 +103,7 @@ class PendingIntentFactory ) fun snoozeNotification(habit: Habit): PendingIntent = - PendingIntent.getBroadcast( + getBroadcast( context, 0, Intent(context, ReminderReceiver::class.java).apply { @@ -114,7 +114,7 @@ class PendingIntentFactory ) fun toggleCheckmark(habit: Habit, timestamp: Long?): PendingIntent = - PendingIntent.getBroadcast( + getBroadcast( context, 2, Intent(context, WidgetReceiver::class.java).apply { @@ -145,7 +145,7 @@ class PendingIntentFactory ) fun updateWidgets(): PendingIntent = - PendingIntent.getBroadcast( + getBroadcast( context, 0, Intent(context, WidgetReceiver::class.java).apply { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/notifications/RingtoneManager.kt b/uhabits-android/src/main/java/org/isoron/uhabits/notifications/RingtoneManager.kt index f1db7d29e..1e79ae857 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/notifications/RingtoneManager.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/notifications/RingtoneManager.kt @@ -40,17 +40,17 @@ class RingtoneManager PreferenceManager.getDefaultSharedPreferences(context) fun getName(): String? { - try { + return try { var ringtoneName = context.resources.getString(R.string.none) val ringtoneUri = getURI() if (ringtoneUri != null) { val ringtone = getRingtone(context, ringtoneUri) if (ringtone != null) ringtoneName = ringtone.getTitle(context) } - return ringtoneName + ringtoneName } catch (e: RuntimeException) { e.printStackTrace() - return null + null } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/sync/RemoteSyncServer.kt b/uhabits-android/src/main/java/org/isoron/uhabits/sync/RemoteSyncServer.kt index ae64042b7..4e8f4a705 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/sync/RemoteSyncServer.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/sync/RemoteSyncServer.kt @@ -80,8 +80,7 @@ class RemoteSyncServer( try { val url = "${preferences.syncBaseURL}/db/$key" Log.i("RemoteSyncServer", "GET $url") - val data: SyncData = httpClient.get(url) - return@IO data + return@IO httpClient.get(url) } catch (e: ServerResponseException) { throw ServiceUnavailable() } catch (e: ClientRequestException) { diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.java b/uhabits-android/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.java deleted file mode 100644 index db66fd774..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.*; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.*; - -import org.jetbrains.annotations.*; - -public class AttributeSetUtils -{ - public static final String ISORON_NAMESPACE = "http://isoron.org/android"; - - @Nullable - public static String getAttribute(@NonNull Context context, - @NonNull AttributeSet attrs, - @NonNull String name, - @Nullable String defaultValue) - { - int resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0); - if (resId != 0) return context.getResources().getString(resId); - - String value = attrs.getAttributeValue(ISORON_NAMESPACE, name); - if (value != null) return value; - else return defaultValue; - } - - public static boolean getBooleanAttribute(@NonNull Context context, - @NonNull AttributeSet attrs, - @NonNull String name, - boolean defaultValue) - { - String boolText = getAttribute(context, attrs, name, null); - if (boolText != null) return Boolean.parseBoolean(boolText); - else return defaultValue; - } - - @Contract("_,_,_,!null -> !null") - public static Integer getColorAttribute(@NonNull Context context, - @NonNull AttributeSet attrs, - @NonNull String name, - @Nullable Integer defaultValue) - { - int resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0); - if (resId != 0) return context.getResources().getColor(resId); - else return defaultValue; - } - - public static float getFloatAttribute(@NonNull Context context, - @NonNull AttributeSet attrs, - @NonNull String name, - float defaultValue) - { - try - { - String number = getAttribute(context, attrs, name, null); - if (number != null) return Float.parseFloat(number); - else return defaultValue; - } catch(NumberFormatException e) { - return defaultValue; - } - } - - public static int getIntAttribute(@NonNull Context context, - @NonNull AttributeSet attrs, - @NonNull String name, - int defaultValue) - { - String number = getAttribute(context, attrs, name, null); - if (number != null) return Integer.parseInt(number); - else return defaultValue; - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.kt b/uhabits-android/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.kt new file mode 100644 index 000000000..7ab5a1e34 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016-2021 Á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.util.AttributeSet +import org.jetbrains.annotations.Contract + +object AttributeSetUtils { + const val ISORON_NAMESPACE = "http://isoron.org/android" + @JvmStatic + fun getAttribute( + context: Context, + attrs: AttributeSet, + name: String, + defaultValue: String? + ): String? { + val resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0) + if (resId != 0) return context.resources.getString(resId) + val value = attrs.getAttributeValue(ISORON_NAMESPACE, name) + return value ?: defaultValue + } + + @JvmStatic + fun getBooleanAttribute( + context: Context, + attrs: AttributeSet, + name: String, + defaultValue: Boolean + ): Boolean { + val boolText = getAttribute(context, attrs, name, null) + return if (boolText != null) java.lang.Boolean.parseBoolean(boolText) else defaultValue + } + + @JvmStatic + @Contract("_,_,_,!null -> !null") + fun getColorAttribute( + context: Context, + attrs: AttributeSet, + name: String, + defaultValue: Int? + ): Int? { + val resId = attrs.getAttributeResourceValue(ISORON_NAMESPACE, name, 0) + return if (resId != 0) context.resources.getColor(resId) else defaultValue + } + + @JvmStatic + fun getFloatAttribute( + context: Context, + attrs: AttributeSet, + name: String, + defaultValue: Float + ): Float { + return try { + val number = getAttribute(context, attrs, name, null) + number?.toFloat() ?: defaultValue + } catch (e: NumberFormatException) { + defaultValue + } + } + + fun getIntAttribute( + context: Context, + attrs: AttributeSet, + name: String, + defaultValue: Int + ): Int { + val number = getAttribute(context, attrs, name, null) + return number?.toInt() ?: defaultValue + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java b/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java deleted file mode 100644 index 78aa5a3e7..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.*; -import android.database.sqlite.*; -import android.util.*; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.utils.*; - -import java.io.*; -import java.text.*; - -import static org.isoron.uhabits.core.ConstantsKt.*; - -public abstract class DatabaseUtils -{ - @Nullable - private static HabitsDatabaseOpener opener = null; - - @NonNull - public static File getDatabaseFile(Context context) - { - String databaseFilename = getDatabaseFilename(); - String root = context.getFilesDir().getPath(); - - String format = "%s/../databases/%s"; - String filename = String.format(format, root, databaseFilename); - - return new File(filename); - } - - @NonNull - public static String getDatabaseFilename() - { - String databaseFilename = DATABASE_FILENAME; - if (HabitsApplication.Companion.isTestMode()) databaseFilename = "test.db"; - return databaseFilename; - } - - @SuppressWarnings("unchecked") - public static void initializeDatabase(Context context) - { - opener = new HabitsDatabaseOpener(context, getDatabaseFilename(), - DATABASE_VERSION); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - public static String saveDatabaseCopy(Context context, File dir) - throws IOException - { - SimpleDateFormat dateFormat = DateFormats.getBackupDateFormat(); - String date = dateFormat.format(DateUtils.getLocalTime()); - String format = "%s/Loop Habits Backup %s.db"; - String filename = String.format(format, dir.getAbsolutePath(), date); - Log.i("DatabaseUtils", "Writing: " + filename); - - File db = getDatabaseFile(context); - File dbCopy = new File(filename); - FileUtilsKt.copyTo(db, dbCopy); - - return dbCopy.getAbsolutePath(); - } - - @NonNull - public static SQLiteDatabase openDatabase() - { - if (opener == null) throw new IllegalStateException(); - return opener.getWritableDatabase(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.kt b/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.kt new file mode 100644 index 000000000..db25f279d --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/utils/DatabaseUtils.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016-2021 Á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.database.sqlite.SQLiteDatabase +import android.util.Log +import org.isoron.uhabits.HabitsApplication.Companion.isTestMode +import org.isoron.uhabits.HabitsDatabaseOpener +import org.isoron.uhabits.core.DATABASE_FILENAME +import org.isoron.uhabits.core.DATABASE_VERSION +import org.isoron.uhabits.core.utils.DateFormats.Companion.getBackupDateFormat +import org.isoron.uhabits.core.utils.DateUtils.Companion.getLocalTime +import java.io.File +import java.io.IOException +import java.text.SimpleDateFormat + +object DatabaseUtils { + private var opener: HabitsDatabaseOpener? = null + @JvmStatic + fun getDatabaseFile(context: Context): File { + val databaseFilename = databaseFilename + val root = context.filesDir.path + return File("$root/../databases/$databaseFilename") + } + + private val databaseFilename: String + get() { + var databaseFilename: String = DATABASE_FILENAME + if (isTestMode()) databaseFilename = "test.db" + return databaseFilename + } + + fun initializeDatabase(context: Context?) { + opener = HabitsDatabaseOpener( + context!!, + databaseFilename, + DATABASE_VERSION + ) + } + + @JvmStatic + @Throws(IOException::class) + fun saveDatabaseCopy(context: Context, dir: File): String { + val dateFormat: SimpleDateFormat = getBackupDateFormat() + val date = dateFormat.format(getLocalTime()) + val filename = "${dir.absolutePath}/Loop Habits Backup $date.db" + Log.i("DatabaseUtils", "Writing: $filename") + val db = getDatabaseFile(context) + val dbCopy = File(filename) + db.copyTo(dbCopy) + return dbCopy.absolutePath + } + + fun openDatabase(): SQLiteDatabase { + checkNotNull(opener) + return opener!!.writableDatabase + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/utils/SystemUtils.java b/uhabits-android/src/main/java/org/isoron/uhabits/utils/SystemUtils.kt similarity index 55% rename from uhabits-android/src/main/java/org/isoron/uhabits/utils/SystemUtils.java rename to uhabits-android/src/main/java/org/isoron/uhabits/utils/SystemUtils.kt index d848a90eb..a7d805103 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/utils/SystemUtils.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/utils/SystemUtils.kt @@ -16,30 +16,24 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.utils -package org.isoron.uhabits.utils; +import android.app.Activity +import android.app.KeyguardManager +import android.content.Context +import android.os.Build +import android.view.WindowManager -import android.app.*; -import android.content.*; -import android.os.*; -import android.view.*; +object SystemUtils { + val isAndroidOOrLater: Boolean + get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - -public class SystemUtils -{ - public static boolean isAndroidOOrLater() - { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - } - - public static void unlockScreen(Activity activity) - { - if (isAndroidOOrLater()) { - KeyguardManager km = - (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE); - km.requestDismissKeyguard(activity, null); + fun unlockScreen(activity: Activity) { + if (isAndroidOOrLater) { + val km = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + km.requestDismissKeyguard(activity, null) } else { - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + activity.window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) } } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java deleted file mode 100644 index 8c35f6cbc..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.app.*; -import android.content.*; -import android.graphics.*; -import android.view.*; -import android.widget.*; - -import androidx.annotation.NonNull; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.intents.*; - -import static android.view.View.MeasureSpec.makeMeasureSpec; - -public abstract class BaseWidget -{ - private final int id; - - @NonNull - protected final WidgetPreferences widgetPrefs; - - @NonNull - protected final Preferences prefs; - - @NonNull - protected final PendingIntentFactory pendingIntentFactory; - - @NonNull - private final Context context; - - @NonNull - protected final CommandRunner commandRunner; - - @NonNull - private WidgetDimensions dimensions; - - public BaseWidget(@NonNull Context context, int id) - { - this.id = id; - this.context = context; - - HabitsApplication app = - (HabitsApplication) context.getApplicationContext(); - - widgetPrefs = app.getComponent().getWidgetPreferences(); - prefs = app.getComponent().getPreferences(); - commandRunner = app.getComponent().getCommandRunner(); - pendingIntentFactory = app.getComponent().getPendingIntentFactory(); - dimensions = new WidgetDimensions(getDefaultWidth(), getDefaultHeight(), - getDefaultWidth(), getDefaultHeight()); - } - - public void delete() - { - widgetPrefs.removeWidget(id); - } - - @NonNull - public Context getContext() - { - return context; - } - - public int getId() - { - return id; - } - - @NonNull - public RemoteViews getLandscapeRemoteViews() - { - return getRemoteViews(dimensions.getLandscapeWidth(), - dimensions.getLandscapeHeight()); - } - - public abstract PendingIntent getOnClickPendingIntent(Context context); - - @NonNull - public RemoteViews getPortraitRemoteViews() - { - return getRemoteViews(dimensions.getPortraitWidth(), - dimensions.getPortraitHeight()); - } - - public abstract void refreshData(View widgetView); - - public void setDimensions(@NonNull WidgetDimensions dimensions) - { - this.dimensions = dimensions; - } - - protected abstract View buildView(); - - protected abstract int getDefaultHeight(); - - protected abstract int getDefaultWidth(); - - private void adjustRemoteViewsPadding(RemoteViews remoteViews, - View view, - int width, - int height) - { - int imageWidth = view.getMeasuredWidth(); - int imageHeight = view.getMeasuredHeight(); - int p[] = calculatePadding(width, height, imageWidth, imageHeight); - remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3]); - } - - private void buildRemoteViews(View view, - RemoteViews remoteViews, - int width, - int height) - { - Bitmap bitmap = getBitmapFromView(view); - remoteViews.setImageViewBitmap(R.id.imageView, bitmap); - - adjustRemoteViewsPadding(remoteViews, view, width, height); - - PendingIntent onClickIntent = getOnClickPendingIntent(context); - if (onClickIntent != null) - remoteViews.setOnClickPendingIntent(R.id.button, onClickIntent); - } - - private int[] calculatePadding(int entireWidth, - int entireHeight, - int imageWidth, - int imageHeight) - { - int w = (int) (((float) entireWidth - imageWidth) / 2); - int h = (int) (((float) entireHeight - imageHeight) / 2); - - return new int[]{w, h, w, h}; - } - - @NonNull - private Bitmap getBitmapFromView(View view) - { - view.invalidate(); - int width = view.getMeasuredWidth(); - int height = view.getMeasuredHeight(); - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - view.draw(canvas); - return bitmap; - } - - @NonNull - protected RemoteViews getRemoteViews(int width, int height) - { - View view = buildView(); - measureView(view, width, height); - - refreshData(view); - - if (view.isLayoutRequested()) measureView(view, width, height); - - RemoteViews remoteViews = - new RemoteViews(context.getPackageName(), R.layout.widget_wrapper); - - buildRemoteViews(view, remoteViews, width, height); - - return remoteViews; - } - - private void measureView(View view, int width, int height) - { - LayoutInflater inflater = LayoutInflater.from(context); - View entireView = inflater.inflate(R.layout.widget_wrapper, null); - - int specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY); - int specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY); - - entireView.measure(specWidth, specHeight); - entireView.layout(0, 0, entireView.getMeasuredWidth(), - entireView.getMeasuredHeight()); - - View imageView = entireView.findViewById(R.id.imageView); - width = imageView.getMeasuredWidth(); - height = imageView.getMeasuredHeight(); - - specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY); - specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY); - - view.measure(specWidth, specHeight); - view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); - } - - protected int getPreferedBackgroundAlpha() { - return prefs.getWidgetOpacity(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.kt new file mode 100644 index 000000000..aef9a3a4c --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidget.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016-2021 Á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.app.PendingIntent +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.view.LayoutInflater +import android.view.View +import android.view.View.MeasureSpec +import android.widget.RemoteViews +import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.R +import org.isoron.uhabits.core.commands.CommandRunner +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.preferences.WidgetPreferences +import org.isoron.uhabits.intents.PendingIntentFactory + +abstract class BaseWidget(val context: Context, val id: Int) { + protected val widgetPrefs: WidgetPreferences + protected val prefs: Preferences + protected val pendingIntentFactory: PendingIntentFactory + protected val commandRunner: CommandRunner + private var dimensions: WidgetDimensions + fun delete() { + widgetPrefs.removeWidget(id) + } + + val landscapeRemoteViews: RemoteViews + get() = getRemoteViews( + dimensions.landscapeWidth, + dimensions.landscapeHeight + ) + + abstract fun getOnClickPendingIntent(context: Context): PendingIntent? + val portraitRemoteViews: RemoteViews + get() = getRemoteViews( + dimensions.portraitWidth, + dimensions.portraitHeight + ) + + abstract fun refreshData(widgetView: View) + fun setDimensions(dimensions: WidgetDimensions) { + this.dimensions = dimensions + } + + protected abstract fun buildView(): View? + protected abstract val defaultHeight: Int + protected abstract val defaultWidth: Int + private fun adjustRemoteViewsPadding( + remoteViews: RemoteViews, + view: View, + width: Int, + height: Int + ) { + val imageWidth = view.measuredWidth + val imageHeight = view.measuredHeight + val p = calculatePadding(width, height, imageWidth, imageHeight) + remoteViews.setViewPadding(R.id.buttonOverlay, p[0], p[1], p[2], p[3]) + } + + private fun buildRemoteViews( + view: View, + remoteViews: RemoteViews, + width: Int, + height: Int + ) { + val bitmap = getBitmapFromView(view) + remoteViews.setImageViewBitmap(R.id.imageView, bitmap) + adjustRemoteViewsPadding(remoteViews, view, width, height) + val onClickIntent = getOnClickPendingIntent(context) + if (onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.button, onClickIntent) + } + + private fun calculatePadding( + entireWidth: Int, + entireHeight: Int, + imageWidth: Int, + imageHeight: Int + ): IntArray { + val w = ((entireWidth.toFloat() - imageWidth) / 2).toInt() + val h = ((entireHeight.toFloat() - imageHeight) / 2).toInt() + return intArrayOf(w, h, w, h) + } + + private fun getBitmapFromView(view: View): Bitmap { + view.invalidate() + val width = view.measuredWidth + val height = view.measuredHeight + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + view.draw(canvas) + return bitmap + } + + protected open fun getRemoteViews(width: Int, height: Int): RemoteViews { + val view = buildView()!! + measureView(view, width, height) + refreshData(view) + if (view.isLayoutRequested) measureView(view, width, height) + val remoteViews = RemoteViews(context.packageName, R.layout.widget_wrapper) + buildRemoteViews(view, remoteViews, width, height) + return remoteViews + } + + private fun measureView(view: View, width: Int, height: Int) { + var width = width + var height = height + val inflater = LayoutInflater.from(context) + val entireView = inflater.inflate(R.layout.widget_wrapper, null) + var specWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY) + var specHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + entireView.measure(specWidth, specHeight) + entireView.layout( + 0, + 0, + entireView.measuredWidth, + entireView.measuredHeight + ) + val imageView = entireView.findViewById(R.id.imageView) + width = imageView.measuredWidth + height = imageView.measuredHeight + specWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY) + specHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + view.measure(specWidth, specHeight) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + } + + protected val preferedBackgroundAlpha: Int + protected get() = prefs.widgetOpacity + + init { + val app = context.applicationContext as HabitsApplication + widgetPrefs = app.component.widgetPreferences + prefs = app.component.preferences + commandRunner = app.component.commandRunner + pendingIntentFactory = app.component.pendingIntentFactory + dimensions = WidgetDimensions( + defaultWidth, + defaultHeight, + defaultWidth, + defaultHeight + ) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java deleted file mode 100644 index 946a7e966..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.*; -import android.content.*; -import android.os.*; -import android.widget.*; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; - -import java.util.*; - -import static android.appwidget.AppWidgetManager.*; -import static org.isoron.uhabits.utils.InterfaceUtils.dpToPixels; - -public abstract class BaseWidgetProvider extends AppWidgetProvider -{ - private HabitList habits; - - private Preferences preferences; - - private WidgetPreferences widgetPrefs; - - public static void updateAppWidget(@NonNull AppWidgetManager manager, - @NonNull BaseWidget widget) - { - RemoteViews landscape = widget.getLandscapeRemoteViews(); - RemoteViews portrait = widget.getPortraitRemoteViews(); - RemoteViews views = new RemoteViews(landscape, portrait); - manager.updateAppWidget(widget.getId(), views); - } - - @NonNull - public WidgetDimensions getDimensionsFromOptions(@NonNull Context ctx, - @NonNull Bundle options) - { - int maxWidth = - (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_WIDTH)); - int maxHeight = - (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_HEIGHT)); - int minWidth = - (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_WIDTH)); - int minHeight = - (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_HEIGHT)); - - return new WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight); - } - - @Override - public void onAppWidgetOptionsChanged(@Nullable Context context, - @Nullable AppWidgetManager manager, - int widgetId, - @Nullable Bundle options) - { - try - { - if (context == null) throw new RuntimeException("context is null"); - if (manager == null) throw new RuntimeException("manager is null"); - if (options == null) throw new RuntimeException("options is null"); - updateDependencies(context); - - context.setTheme(R.style.WidgetTheme); - - BaseWidget widget = getWidgetFromId(context, widgetId); - WidgetDimensions dims = getDimensionsFromOptions(context, options); - widget.setDimensions(dims); - updateAppWidget(manager, widget); - } - catch (RuntimeException e) - { - drawErrorWidget(context, manager, widgetId, e); - e.printStackTrace(); - } - } - - @Override - public void onDeleted(@Nullable Context context, @Nullable int[] ids) - { - if (context == null) throw new RuntimeException("context is null"); - if (ids == null) throw new RuntimeException("ids is null"); - - updateDependencies(context); - - for (int id : ids) - { - try - { - BaseWidget widget = getWidgetFromId(context, id); - widget.delete(); - } - catch (HabitNotFoundException e) - { - e.printStackTrace(); - } - } - } - - @Override - public void onUpdate(@Nullable Context context, - @Nullable AppWidgetManager manager, - @Nullable int[] widgetIds) - { - if (context == null) throw new RuntimeException("context is null"); - if (manager == null) throw new RuntimeException("manager is null"); - if (widgetIds == null) throw new RuntimeException("widgetIds is null"); - updateDependencies(context); - context.setTheme(R.style.WidgetTheme); - - new Thread(() -> - { - Looper.prepare(); - for (int id : widgetIds) - update(context, manager, id); - }).start(); - } - - protected List getHabitsFromWidgetId(int widgetId) - { - long selectedIds[] = widgetPrefs.getHabitIdsFromWidgetId(widgetId); - ArrayList selectedHabits = new ArrayList<>(selectedIds.length); - for (long id : selectedIds) - { - Habit h = habits.getById(id); - if (h == null) throw new HabitNotFoundException(); - selectedHabits.add(h); - } - - return selectedHabits; - } - - @NonNull - protected abstract BaseWidget getWidgetFromId(@NonNull Context context, - int id); - - private void drawErrorWidget(Context context, - AppWidgetManager manager, - int widgetId, - RuntimeException e) - { - RemoteViews errorView = - new RemoteViews(context.getPackageName(), R.layout.widget_error); - - if (e instanceof HabitNotFoundException) - { - errorView.setCharSequence(R.id.label, "setText", - context.getString(R.string.habit_not_found)); - } - - manager.updateAppWidget(widgetId, errorView); - } - - private void update(@NonNull Context context, - @NonNull AppWidgetManager manager, - int widgetId) - { - try - { - BaseWidget widget = getWidgetFromId(context, widgetId); - Bundle options = manager.getAppWidgetOptions(widgetId); - widget.setDimensions(getDimensionsFromOptions(context, options)); - updateAppWidget(manager, widget); - } - catch (RuntimeException e) - { - drawErrorWidget(context, manager, widgetId, e); - e.printStackTrace(); - } - } - - private void updateDependencies(Context context) - { - HabitsApplication app = - (HabitsApplication) context.getApplicationContext(); - habits = app.getComponent().getHabitList(); - preferences = app.getComponent().getPreferences(); - widgetPrefs = app.getComponent().getWidgetPreferences(); - } - - public Preferences getPreferences() - { - return preferences; - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt new file mode 100644 index 000000000..1a41d0f4f --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2016-2021 Á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.appwidget.AppWidgetProvider +import android.content.Context +import android.os.Bundle +import android.os.Looper +import android.widget.RemoteViews +import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.R +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.HabitNotFoundException +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.preferences.WidgetPreferences +import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels +import java.util.ArrayList + +abstract class BaseWidgetProvider : AppWidgetProvider() { + private lateinit var habits: HabitList + lateinit var preferences: Preferences + private set + private lateinit var widgetPrefs: WidgetPreferences + fun getDimensionsFromOptions( + ctx: Context, + options: Bundle + ): WidgetDimensions { + val maxWidth = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH).toFloat() + ).toInt() + val maxHeight = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT).toFloat() + ).toInt() + val minWidth = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH).toFloat() + ).toInt() + val minHeight = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT).toFloat() + ).toInt() + return WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight) + } + + override fun onAppWidgetOptionsChanged( + context: Context, + manager: AppWidgetManager, + widgetId: Int, + options: Bundle + ) { + try { + updateDependencies(context) + context.setTheme(R.style.WidgetTheme) + val widget = getWidgetFromId(context, widgetId) + val dims = getDimensionsFromOptions(context, options) + widget.setDimensions(dims) + updateAppWidget(manager, widget) + } catch (e: RuntimeException) { + drawErrorWidget(context, manager, widgetId, e) + e.printStackTrace() + } + } + + override fun onDeleted(context: Context?, ids: IntArray?) { + if (context == null) throw RuntimeException("context is null") + if (ids == null) throw RuntimeException("ids is null") + updateDependencies(context) + for (id in ids) { + try { + val widget = getWidgetFromId(context, id) + widget.delete() + } catch (e: HabitNotFoundException) { + e.printStackTrace() + } + } + } + + override fun onUpdate( + context: Context, + manager: AppWidgetManager, + widgetIds: IntArray + ) { + updateDependencies(context) + context.setTheme(R.style.WidgetTheme) + Thread { + Looper.prepare() + for (id in widgetIds) update(context, manager, id) + }.start() + } + + protected fun getHabitsFromWidgetId(widgetId: Int): List { + val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId) + val selectedHabits = ArrayList(selectedIds.size) + for (id in selectedIds) { + val h = habits.getById(id) ?: throw HabitNotFoundException() + selectedHabits.add(h) + } + return selectedHabits + } + + protected abstract fun getWidgetFromId( + context: Context, + id: Int + ): BaseWidget + + private fun drawErrorWidget( + context: Context, + manager: AppWidgetManager, + widgetId: Int, + e: RuntimeException + ) { + val errorView = RemoteViews(context.packageName, R.layout.widget_error) + if (e is HabitNotFoundException) { + errorView.setCharSequence( + R.id.label, + "setText", + context.getString(R.string.habit_not_found) + ) + } + manager.updateAppWidget(widgetId, errorView) + } + + private fun update( + context: Context, + manager: AppWidgetManager, + widgetId: Int + ) { + try { + val widget = getWidgetFromId(context, widgetId) + val options = manager.getAppWidgetOptions(widgetId) + widget.setDimensions(getDimensionsFromOptions(context, options)) + updateAppWidget(manager, widget) + } catch (e: RuntimeException) { + drawErrorWidget(context, manager, widgetId, e) + e.printStackTrace() + } + } + + private fun updateDependencies(context: Context) { + val app = context.applicationContext as HabitsApplication + habits = app.component.habitList + preferences = app.component.preferences + widgetPrefs = app.component.widgetPreferences + } + + companion object { + fun updateAppWidget( + manager: AppWidgetManager, + widget: BaseWidget + ) { + val landscape = widget.landscapeRemoteViews + val portrait = widget.portraitRemoteViews + val views = RemoteViews(landscape, portrait) + manager.updateAppWidget(widget.id, views) + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt index a62bc910a..95d0394bf 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt @@ -21,7 +21,9 @@ package org.isoron.uhabits.widgets import android.app.PendingIntent import android.content.Context +import android.os.Build import android.view.View +import androidx.annotation.RequiresApi import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.utils.DateUtils @@ -31,10 +33,13 @@ import org.isoron.uhabits.widgets.views.CheckmarkWidgetView open class CheckmarkWidget( context: Context, widgetId: Int, - protected val habit: Habit + protected val habit: Habit, ) : BaseWidget(context, widgetId) { - override fun getOnClickPendingIntent(context: Context): PendingIntent { + override val defaultHeight: Int = 125 + override val defaultWidth: Int = 125 + + override fun getOnClickPendingIntent(context: Context): PendingIntent? { return if (habit.isNumerical) { pendingIntentFactory.setNumericalValue(context, habit, 10, null) } else { @@ -42,20 +47,21 @@ open class CheckmarkWidget( } } - override fun refreshData(v: View) { - (v as CheckmarkWidgetView).apply { + @RequiresApi(Build.VERSION_CODES.O) + override fun refreshData(widgetView: View) { + (widgetView as CheckmarkWidgetView).apply { val today = DateUtils.getTodayWithOffset() setBackgroundAlpha(preferedBackgroundAlpha) - setActiveColor(habit.color.toThemedAndroidColor(context)) - setName(habit.name) - setEntryValue(habit.computedEntries.get(today).value) + activeColor = habit.color.toThemedAndroidColor(context) + name = habit.name + entryValue = habit.computedEntries.get(today).value if (habit.isNumerical) { - setNumerical(true) - setEntryState(getNumericalEntryState()) + isNumerical = true + entryState = getNumericalEntryState() } else { - setEntryState(habit.computedEntries.get(today).value) + entryState = habit.computedEntries.get(today).value } - setPercentage(habit.scores.get(today).value.toFloat()) + percentage = habit.scores[today].value.toFloat() refresh() } } @@ -64,9 +70,6 @@ open class CheckmarkWidget( return CheckmarkWidgetView(context) } - override fun getDefaultHeight() = 125 - override fun getDefaultWidth() = 125 - private fun getNumericalEntryState(): Int { return if (habit.isCompletedToday()) { Entry.YES_MANUAL diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.kt index b5535291d..af8010014 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.kt @@ -23,7 +23,7 @@ import android.content.Context class CheckmarkWidgetProvider : BaseWidgetProvider() { override fun getWidgetFromId(context: Context, id: Int): BaseWidget { val habits = getHabitsFromWidgetId(id) - if (habits.size == 1) return CheckmarkWidget(context, id, habits[0]) - else return StackWidget(context, id, StackWidgetType.CHECKMARK, habits) + return if (habits.size == 1) CheckmarkWidget(context, id, habits[0]) + else StackWidget(context, id, StackWidgetType.CHECKMARK, habits) } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/EmptyWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/EmptyWidget.kt index 2f95ee987..1e0c2bad5 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/EmptyWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/EmptyWidget.kt @@ -19,18 +19,19 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import org.isoron.uhabits.widgets.views.EmptyWidgetView class EmptyWidget( context: Context, - widgetId: Int + widgetId: Int, ) : BaseWidget(context, widgetId) { + override val defaultHeight: Int = 200 + override val defaultWidth: Int = 200 - override fun getOnClickPendingIntent(context: Context) = null + override fun getOnClickPendingIntent(context: Context): PendingIntent? = null override fun refreshData(v: View) {} override fun buildView() = EmptyWidgetView(context) - override fun getDefaultHeight() = 200 - override fun getDefaultWidth() = 200 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt index 09c969cab..136bafceb 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import org.isoron.uhabits.activities.common.views.FrequencyChart @@ -30,10 +31,12 @@ class FrequencyWidget( context: Context, widgetId: Int, private val habit: Habit, - private val firstWeekday: Int + private val firstWeekday: Int, ) : BaseWidget(context, widgetId) { + override val defaultHeight: Int = 200 + override val defaultWidth: Int = 200 - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent = pendingIntentFactory.showHabit(habit) override fun refreshData(v: View) { @@ -50,7 +53,4 @@ class FrequencyWidget( override fun buildView() = GraphWidgetView(context, FrequencyChart(context)) - - override fun getDefaultHeight() = 200 - override fun getDefaultWidth() = 200 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt index 907264c1c..3d380ec25 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt @@ -24,12 +24,12 @@ import android.content.Context class FrequencyWidgetProvider : BaseWidgetProvider() { override fun getWidgetFromId(context: Context, id: Int): BaseWidget { val habits = getHabitsFromWidgetId(id) - if (habits.size == 1) return FrequencyWidget( + return if (habits.size == 1) FrequencyWidget( context, id, habits[0], preferences.firstWeekdayInt ) - else return StackWidget(context, id, StackWidgetType.FREQUENCY, habits) + else StackWidget(context, id, StackWidgetType.FREQUENCY, habits) } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt index e39ff405b..7cf32e46f 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidget.kt @@ -35,9 +35,12 @@ import java.util.Locale class HistoryWidget( context: Context, id: Int, - private val habit: Habit + private val habit: Habit, ) : BaseWidget(context, id) { + override val defaultHeight: Int = 250 + override val defaultWidth: Int = 250 + override fun getOnClickPendingIntent(context: Context): PendingIntent { return pendingIntentFactory.showHabit(habit) } @@ -72,7 +75,4 @@ class HistoryWidget( ).apply { setTitle(habit.name) } - - override fun getDefaultHeight() = 250 - override fun getDefaultWidth() = 250 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.kt index f673911e0..c9dd8da66 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.kt @@ -23,11 +23,11 @@ import android.content.Context class HistoryWidgetProvider : BaseWidgetProvider() { override fun getWidgetFromId(context: Context, id: Int): BaseWidget { val habits = getHabitsFromWidgetId(id) - if (habits.size == 1) return HistoryWidget( + return if (habits.size == 1) HistoryWidget( context, id, habits[0] ) - else return StackWidget(context, id, StackWidgetType.HISTORY, habits) + else StackWidget(context, id, StackWidgetType.HISTORY, habits) } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt index ad2f970c5..4beeed281 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import org.isoron.uhabits.activities.common.views.ScoreChart @@ -30,10 +31,12 @@ import org.isoron.uhabits.widgets.views.GraphWidgetView class ScoreWidget( context: Context, id: Int, - private val habit: Habit + private val habit: Habit, ) : BaseWidget(context, id) { + override val defaultHeight: Int = 300 + override val defaultWidth: Int = 300 - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent = pendingIntentFactory.showHabit(habit) override fun refreshData(view: View) { @@ -57,7 +60,4 @@ class ScoreWidget( GraphWidgetView(context, ScoreChart(context)).apply { setTitle(habit.name) } - - override fun getDefaultHeight() = 300 - override fun getDefaultWidth() = 300 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.kt index 221f04bc3..f271fb799 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.kt @@ -23,7 +23,7 @@ import android.content.Context class ScoreWidgetProvider : BaseWidgetProvider() { override fun getWidgetFromId(context: Context, id: Int): BaseWidget { val habits = getHabitsFromWidgetId(id) - if (habits.size == 1) return ScoreWidget(context, id, habits[0]) - else return StackWidget(context, id, StackWidgetType.SCORE, habits) + return if (habits.size == 1) ScoreWidget(context, id, habits[0]) + else StackWidget(context, id, StackWidgetType.SCORE, habits) } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt index 51c4ef496..106fd2b2f 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.content.Context import android.content.Intent @@ -32,15 +33,22 @@ class StackWidget( context: Context, widgetId: Int, private val widgetType: StackWidgetType, - private val habits: List + private val habits: List, ) : BaseWidget(context, widgetId) { + override val defaultHeight: Int = 0 + override val defaultWidth: Int = 0 - override fun getOnClickPendingIntent(context: Context) = null + override fun getOnClickPendingIntent(context: Context): PendingIntent? = null override fun refreshData(v: View) { // unused } + override fun buildView(): View? { + // unused + return null + } + override fun getRemoteViews(width: Int, height: Int): RemoteViews { val manager = AppWidgetManager.getInstance(context) val remoteViews = RemoteViews(context.packageName, StackWidgetType.getStackWidgetLayoutId(widgetType)) @@ -59,8 +67,4 @@ class StackWidget( ) return remoteViews } - - override fun buildView() = null // unused - override fun getDefaultHeight() = 0 // unused - override fun getDefaultWidth() = 0 // unused } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.java deleted file mode 100644 index d5b7fe110..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.*; -import android.content.*; -import android.os.*; -import android.util.Log; -import android.widget.*; - -import androidx.annotation.NonNull; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.utils.*; - -import java.util.*; - -import static android.appwidget.AppWidgetManager.*; -import static org.isoron.uhabits.utils.InterfaceUtils.dpToPixels; -import static org.isoron.uhabits.widgets.StackWidgetService.*; - -public class StackWidgetService extends RemoteViewsService -{ - public static final String WIDGET_TYPE = "WIDGET_TYPE"; - public static final String HABIT_IDS = "HABIT_IDS"; - - @Override - public RemoteViewsFactory onGetViewFactory(Intent intent) - { - return new StackRemoteViewsFactory(this.getApplicationContext(), intent); - } -} - -class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory -{ - private Context context; - private int widgetId; - private long[] habitIds; - private StackWidgetType widgetType; - private ArrayList remoteViews = new ArrayList<>(); - - public StackRemoteViewsFactory(Context context, Intent intent) - { - this.context = context; - widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID); - int widgetTypeValue = intent.getIntExtra(WIDGET_TYPE, -1); - String habitIdsStr = intent.getStringExtra(HABIT_IDS); - - if (widgetTypeValue < 0) throw new RuntimeException("invalid widget type"); - if (habitIdsStr == null) throw new RuntimeException("habitIdsStr is null"); - - widgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue); - habitIds = StringUtils.splitLongs(habitIdsStr); - } - - public void onCreate() - { - - } - - public void onDestroy() - { - - } - - public int getCount() - { - return habitIds.length; - } - - @NonNull - public WidgetDimensions getDimensionsFromOptions(@NonNull Context ctx, - @NonNull Bundle options) - { - int maxWidth = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_WIDTH)); - int maxHeight = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MAX_HEIGHT)); - int minWidth = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_WIDTH)); - int minHeight = (int) dpToPixels(ctx, options.getInt(OPTION_APPWIDGET_MIN_HEIGHT)); - - return new WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight); - } - - public RemoteViews getViewAt(int position) - { - Log.i("StackRemoteViewsFactory", "getViewAt " + position); - if (position < 0 || position > remoteViews.size()) return null; - return remoteViews.get(position); - } - - @NonNull - private BaseWidget constructWidget(@NonNull Habit habit, - @NonNull Preferences prefs) - { - switch (widgetType) - { - case CHECKMARK: - return new CheckmarkWidget(context, widgetId, habit); - case FREQUENCY: - return new FrequencyWidget(context, widgetId, habit, prefs.getFirstWeekdayInt()); - case SCORE: - return new ScoreWidget(context, widgetId, habit); - case HISTORY: - return new HistoryWidget(context, widgetId, habit); - case STREAKS: - return new StreakWidget(context, widgetId, habit); - } - - throw new IllegalStateException(); - } - - public RemoteViews getLoadingView() - { - Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId); - EmptyWidget widget = new EmptyWidget(context, widgetId); - widget.setDimensions(getDimensionsFromOptions(context, options)); - RemoteViews landscapeViews = widget.getLandscapeRemoteViews(); - RemoteViews portraitViews = widget.getPortraitRemoteViews(); - return new RemoteViews(landscapeViews, portraitViews); - } - - public int getViewTypeCount() - { - return 1; - } - - public long getItemId(int position) - { - return habitIds[position]; - } - - public boolean hasStableIds() - { - return true; - } - - public void onDataSetChanged() - { - Log.i("StackRemoteViewsFactory", "onDataSetChanged started"); - - HabitsApplication app = (HabitsApplication) context.getApplicationContext(); - Preferences prefs = app.getComponent().getPreferences(); - HabitList habitList = app.getComponent().getHabitList(); - Bundle options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId); - ArrayList newRemoteViews = new ArrayList<>(); - - if (Looper.myLooper() == null) Looper.prepare(); - - for (long id : habitIds) - { - Habit h = habitList.getById(id); - if (h == null) throw new HabitNotFoundException(); - - BaseWidget widget = constructWidget(h, prefs); - widget.setDimensions(getDimensionsFromOptions(context, options)); - - RemoteViews landscapeViews = widget.getLandscapeRemoteViews(); - RemoteViews portraitViews = widget.getPortraitRemoteViews(); - newRemoteViews.add(new RemoteViews(landscapeViews, portraitViews)); - - Log.i("StackRemoteViewsFactory", "onDataSetChanged constructed widget " + id); - } - - remoteViews = newRemoteViews; - Log.i("StackRemoteViewsFactory", "onDataSetChanged ended"); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt new file mode 100644 index 000000000..5351a3ceb --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetService.kt @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2016-2021 Á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.Context +import android.content.Intent +import android.os.Bundle +import android.os.Looper +import android.util.Log +import android.widget.RemoteViews +import android.widget.RemoteViewsService +import android.widget.RemoteViewsService.RemoteViewsFactory +import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.HabitNotFoundException +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.utils.StringUtils.Companion.splitLongs +import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels +import java.util.ArrayList + +class StackWidgetService : RemoteViewsService() { + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { + return StackRemoteViewsFactory(this.applicationContext, intent) + } + + companion object { + const val WIDGET_TYPE = "WIDGET_TYPE" + const val HABIT_IDS = "HABIT_IDS" + } +} + +internal class StackRemoteViewsFactory(private val context: Context, intent: Intent) : + RemoteViewsFactory { + private val widgetId: Int = intent.getIntExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + ) + private val habitIds: LongArray + private val widgetType: StackWidgetType? + private var remoteViews = ArrayList() + override fun onCreate() {} + override fun onDestroy() {} + override fun getCount(): Int { + return habitIds.size + } + + fun getDimensionsFromOptions( + ctx: Context, + options: Bundle + ): WidgetDimensions { + val maxWidth = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH).toFloat() + ).toInt() + val maxHeight = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT).toFloat() + ).toInt() + val minWidth = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH).toFloat() + ).toInt() + val minHeight = dpToPixels( + ctx, + options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT).toFloat() + ).toInt() + return WidgetDimensions(minWidth, maxHeight, maxWidth, minHeight) + } + + override fun getViewAt(position: Int): RemoteViews? { + Log.i("StackRemoteViewsFactory", "getViewAt $position") + return if (position < 0 || position > remoteViews.size) null else remoteViews[position] + } + + private fun constructWidget( + habit: Habit, + prefs: Preferences + ): BaseWidget { + when (widgetType) { + StackWidgetType.CHECKMARK -> return CheckmarkWidget(context, widgetId, habit) + StackWidgetType.FREQUENCY -> return FrequencyWidget( + context, + widgetId, + habit, + prefs.firstWeekdayInt + ) + StackWidgetType.SCORE -> return ScoreWidget(context, widgetId, habit) + StackWidgetType.HISTORY -> return HistoryWidget(context, widgetId, habit) + StackWidgetType.STREAKS -> return StreakWidget(context, widgetId, habit) + } + throw IllegalStateException() + } + + override fun getLoadingView(): RemoteViews { + val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId) + val widget = EmptyWidget(context, widgetId) + widget.setDimensions(getDimensionsFromOptions(context, options)) + val landscapeViews = widget.landscapeRemoteViews + val portraitViews = widget.portraitRemoteViews + return RemoteViews(landscapeViews, portraitViews) + } + + override fun getViewTypeCount(): Int { + return 1 + } + + override fun getItemId(position: Int): Long { + return habitIds[position] + } + + override fun hasStableIds(): Boolean { + return true + } + + override fun onDataSetChanged() { + Log.i("StackRemoteViewsFactory", "onDataSetChanged started") + val app = context.applicationContext as HabitsApplication + val prefs = app.component.preferences + val habitList = app.component.habitList + val options = AppWidgetManager.getInstance(context).getAppWidgetOptions(widgetId) + val newRemoteViews = ArrayList() + if (Looper.myLooper() == null) Looper.prepare() + for (id in habitIds) { + val h = habitList.getById(id) ?: throw HabitNotFoundException() + val widget = constructWidget(h, prefs) + widget.setDimensions(getDimensionsFromOptions(context, options)) + val landscapeViews = widget.landscapeRemoteViews + val portraitViews = widget.portraitRemoteViews + newRemoteViews.add(RemoteViews(landscapeViews, portraitViews)) + Log.i("StackRemoteViewsFactory", "onDataSetChanged constructed widget $id") + } + remoteViews = newRemoteViews + Log.i("StackRemoteViewsFactory", "onDataSetChanged ended") + } + + init { + val widgetTypeValue = intent.getIntExtra(StackWidgetService.WIDGET_TYPE, -1) + val habitIdsStr = intent.getStringExtra(StackWidgetService.HABIT_IDS) + if (widgetTypeValue < 0) throw RuntimeException("invalid widget type") + if (habitIdsStr == null) throw RuntimeException("habitIdsStr is null") + widgetType = StackWidgetType.getWidgetTypeFromValue(widgetTypeValue) + habitIds = splitLongs(habitIdsStr) + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.java deleted file mode 100644 index e686e2387..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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 org.isoron.uhabits.R; - -/** - * Created by victoryu on 11/3/17. - */ - -public enum StackWidgetType { - - CHECKMARK(0), - FREQUENCY(1), - SCORE(2), // habit strength widget - HISTORY(3), - STREAKS(4), - TARGET(5); - - private int value; - StackWidgetType(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - public static StackWidgetType getWidgetTypeFromValue(int value) { - if (CHECKMARK.getValue() == value) { - return CHECKMARK; - } else if (FREQUENCY.getValue() == value) { - return FREQUENCY; - } else if (SCORE.getValue() == value) { - return SCORE; - } else if (HISTORY.getValue() == value) { - return HISTORY; - } else if (STREAKS.getValue() == value) { - return STREAKS; - } else if (TARGET.getValue() == value) { - return TARGET; - } - return null; - } - - public static int getStackWidgetLayoutId(StackWidgetType type) { - switch (type) { - case CHECKMARK: - return R.layout.checkmark_stackview_widget; - case FREQUENCY: - return R.layout.frequency_stackview_widget; - case SCORE: - return R.layout.score_stackview_widget; - case HISTORY: - return R.layout.history_stackview_widget; - case STREAKS: - return R.layout.streak_stackview_widget; - case TARGET: - return R.layout.target_stackview_widget; - } - return 0; - } - - public static int getStackWidgetAdapterViewId(StackWidgetType type) { - switch (type) { - case CHECKMARK: - return R.id.checkmarkStackWidgetView; - case FREQUENCY: - return R.id.frequencyStackWidgetView; - case SCORE: - return R.id.scoreStackWidgetView; - case HISTORY: - return R.id.historyStackWidgetView; - case STREAKS: - return R.id.streakStackWidgetView; - case TARGET: - return R.id.targetStackWidgetView; - } - return 0; - } - - public static int getStackWidgetEmptyViewId(StackWidgetType type) { - switch (type) { - case CHECKMARK: - return R.id.checkmarkStackWidgetEmptyView; - case FREQUENCY: - return R.id.frequencyStackWidgetEmptyView; - case SCORE: - return R.id.scoreStackWidgetEmptyView; - case HISTORY: - return R.id.historyStackWidgetEmptyView; - case STREAKS: - return R.id.streakStackWidgetEmptyView; - case TARGET: - return R.id.targetStackWidgetEmptyView; - } - return 0; - } - -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt new file mode 100644 index 000000000..ca120142b --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016-2021 Á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 org.isoron.uhabits.R + +/** + * Created by victoryu on 11/3/17. + */ +enum class StackWidgetType(val value: Int) { + CHECKMARK(0), FREQUENCY(1), SCORE(2), // habit strength widget + HISTORY(3), STREAKS(4), TARGET(5); + + companion object { + fun getWidgetTypeFromValue(value: Int): StackWidgetType? { + return when { + CHECKMARK.value == value -> CHECKMARK + FREQUENCY.value == value -> FREQUENCY + SCORE.value == value -> SCORE + HISTORY.value == value -> HISTORY + STREAKS.value == value -> STREAKS + TARGET.value == value -> TARGET + else -> null + } + } + + fun getStackWidgetLayoutId(type: StackWidgetType?): Int { + when (type) { + CHECKMARK -> return R.layout.checkmark_stackview_widget + FREQUENCY -> return R.layout.frequency_stackview_widget + SCORE -> return R.layout.score_stackview_widget + HISTORY -> return R.layout.history_stackview_widget + STREAKS -> return R.layout.streak_stackview_widget + TARGET -> return R.layout.target_stackview_widget + } + return 0 + } + + fun getStackWidgetAdapterViewId(type: StackWidgetType?): Int { + when (type) { + CHECKMARK -> return R.id.checkmarkStackWidgetView + FREQUENCY -> return R.id.frequencyStackWidgetView + SCORE -> return R.id.scoreStackWidgetView + HISTORY -> return R.id.historyStackWidgetView + STREAKS -> return R.id.streakStackWidgetView + TARGET -> return R.id.targetStackWidgetView + } + return 0 + } + + fun getStackWidgetEmptyViewId(type: StackWidgetType?): Int { + when (type) { + CHECKMARK -> return R.id.checkmarkStackWidgetEmptyView + FREQUENCY -> return R.id.frequencyStackWidgetEmptyView + SCORE -> return R.id.scoreStackWidgetEmptyView + HISTORY -> return R.id.historyStackWidgetEmptyView + STREAKS -> return R.id.streakStackWidgetEmptyView + TARGET -> return R.id.targetStackWidgetEmptyView + } + return 0 + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidget.kt index 0a5658c09..604e760e7 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import android.view.ViewGroup.LayoutParams @@ -31,10 +32,12 @@ import org.isoron.uhabits.widgets.views.GraphWidgetView class StreakWidget( context: Context, id: Int, - private val habit: Habit + private val habit: Habit, ) : BaseWidget(context, id) { + override val defaultHeight: Int = 200 + override val defaultWidth: Int = 200 - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent = pendingIntentFactory.showHabit(habit) override fun refreshData(view: View) { @@ -53,7 +56,4 @@ class StreakWidget( layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) } } - - override fun getDefaultHeight() = 200 - override fun getDefaultWidth() = 200 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.kt index 280c9200c..98b9e8dd9 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.kt @@ -23,7 +23,7 @@ import android.content.Context class StreakWidgetProvider : BaseWidgetProvider() { override fun getWidgetFromId(context: Context, id: Int): BaseWidget { val habits = getHabitsFromWidgetId(id) - if (habits.size == 1) return StreakWidget(context, id, habits[0]) - else return StackWidget(context, id, StackWidgetType.STREAKS, habits) + return if (habits.size == 1) StreakWidget(context, id, habits[0]) + else StackWidget(context, id, StackWidgetType.STREAKS, habits) } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidget.kt index bd1667b55..3c8bf8082 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidget.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidget.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.widgets +import android.app.PendingIntent import android.content.Context import android.view.View import android.view.ViewGroup.LayoutParams @@ -34,10 +35,12 @@ import org.isoron.uhabits.widgets.views.GraphWidgetView class TargetWidget( context: Context, id: Int, - private val habit: Habit + private val habit: Habit, ) : BaseWidget(context, id) { + override val defaultHeight: Int = 200 + override val defaultWidth: Int = 200 - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent = pendingIntentFactory.showHabit(habit) override fun refreshData(view: View) = runBlocking { @@ -58,7 +61,4 @@ class TargetWidget( layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) } } - - override fun getDefaultHeight() = 200 - override fun getDefaultWidth() = 200 } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidgetProvider.kt index 08c2aa731..cb14d61ca 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidgetProvider.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/TargetWidgetProvider.kt @@ -23,7 +23,7 @@ import android.content.Context class TargetWidgetProvider : BaseWidgetProvider() { override fun getWidgetFromId(context: Context, id: Int): BaseWidget { val habits = getHabitsFromWidgetId(id) - if (habits.size == 1) return TargetWidget(context, id, habits[0]) - else return StackWidget(context, id, StackWidgetType.TARGET, habits) + return if (habits.size == 1) TargetWidget(context, id, habits[0]) + else StackWidget(context, id, StackWidgetType.TARGET, habits) } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java deleted file mode 100644 index 930bcdef3..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.views; - -import android.content.*; -import android.util.*; -import android.widget.*; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.activities.habits.list.views.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.activities.common.views.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.inject.*; -import org.isoron.uhabits.utils.*; - -import static org.isoron.uhabits.utils.InterfaceUtils.getDimension; - -public class CheckmarkWidgetView extends HabitWidgetView { - protected int activeColor; - - protected float percentage; - - @Nullable - protected String name; - - protected RingView ring; - - protected TextView label; - - protected int entryValue; - - protected int entryState; - - protected boolean isNumerical; - - private Preferences preferences; - - public CheckmarkWidgetView(Context context) - { - super(context); - init(); - } - - public CheckmarkWidgetView(Context context, AttributeSet attrs) - { - super(context, attrs); - init(); - } - - public void refresh() - { - if (backgroundPaint == null || frame == null || ring == null) return; - - StyledResources res = new StyledResources(getContext()); - - int bgColor; - int fgColor; - - switch (entryState) { - case Entry.YES_MANUAL: - case Entry.SKIP: - bgColor = activeColor; - fgColor = res.getColor(R.attr.highContrastReverseTextColor); - setShadowAlpha(0x4f); - backgroundPaint.setColor(bgColor); - frame.setBackgroundDrawable(background); - break; - - case Entry.YES_AUTO: - case Entry.NO: - case Entry.UNKNOWN: - default: - bgColor = res.getColor(R.attr.cardBgColor); - fgColor = res.getColor(R.attr.mediumContrastTextColor); - setShadowAlpha(0x00); - break; - } - - ring.setPercentage(percentage); - ring.setColor(fgColor); - ring.setBackgroundColor(bgColor); - ring.setText(getText()); - - label.setText(name); - label.setTextColor(fgColor); - - requestLayout(); - postInvalidate(); - } - - public void setEntryState(int entryState) - { - this.entryState = entryState; - } - - protected String getText() - { - if (isNumerical) return NumberButtonViewKt.toShortString(entryValue / 1000.0); - switch (entryState) { - case Entry.YES_MANUAL: - case Entry.YES_AUTO: - return getResources().getString(R.string.fa_check); - case Entry.SKIP: - return getResources().getString(R.string.fa_skipped); - case Entry.UNKNOWN: - { - if (preferences.areQuestionMarksEnabled()) - return getResources().getString(R.string.fa_question); - else - getResources().getString(R.string.fa_times); - } - case Entry.NO: - default: - return getResources().getString(R.string.fa_times); - } - } - - public void setActiveColor(int activeColor) - { - this.activeColor = activeColor; - } - - public void setEntryValue(int entryValue) - { - this.entryValue = entryValue; - } - - public void setName(@NonNull String name) - { - this.name = name; - } - - public void setPercentage(float percentage) - { - this.percentage = percentage; - } - - public void setNumerical(boolean isNumerical) - { - this.isNumerical = isNumerical; - } - - @Override - @NonNull - protected Integer getInnerLayoutId() - { - return R.layout.widget_checkmark; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) - { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - - float w = width; - float h = width * 1.25f; - float scale = Math.min(width / w, height / h); - - w *= scale; - h *= scale; - - if (h < getDimension(getContext(), 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); - - float textSize = 0.15f * h; - float maxTextSize = getDimension(getContext(), R.dimen.smallerTextSize); - textSize = Math.min(textSize, maxTextSize); - - label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); - ring.setTextSize(textSize); - ring.setThickness(0.15f * textSize); - - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private void init() - { - HabitsApplicationComponent appComponent; - appComponent = ((HabitsApplication) getContext().getApplicationContext()).getComponent(); - preferences = appComponent.getPreferences(); - - 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 = PaletteUtils.getAndroidTestColor(6); - entryValue = Entry.YES_MANUAL; - refresh(); - } - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.kt new file mode 100644 index 000000000..a74c7982f --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.kt @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2016-2021 Á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.views + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.View +import android.widget.TextView +import org.isoron.uhabits.HabitsApplication +import org.isoron.uhabits.R +import org.isoron.uhabits.activities.common.views.RingView +import org.isoron.uhabits.activities.habits.list.views.toShortString +import org.isoron.uhabits.core.models.Entry.Companion.NO +import org.isoron.uhabits.core.models.Entry.Companion.SKIP +import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN +import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO +import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.inject.HabitsApplicationComponent +import org.isoron.uhabits.utils.InterfaceUtils.getDimension +import org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor +import org.isoron.uhabits.utils.StyledResources +import kotlin.math.min + +class CheckmarkWidgetView : HabitWidgetView { + var activeColor: Int = 0 + + var percentage = 0f + var name: String? = null + private lateinit var ring: RingView + private lateinit var label: TextView + var entryValue = 0 + var entryState = 0 + var isNumerical = false + private var preferences: Preferences? = null + + constructor(context: Context?) : super(context) { + init() + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init() + } + + fun refresh() { + if (backgroundPaint == null || frame == null) return + val res = StyledResources(context) + val bgColor: Int + val fgColor: Int + when (entryState) { + YES_MANUAL, SKIP -> { + bgColor = activeColor + fgColor = res.getColor(R.attr.highContrastReverseTextColor) + setShadowAlpha(0x4f) + backgroundPaint!!.color = bgColor + frame!!.setBackgroundDrawable(background) + } + YES_AUTO, NO, UNKNOWN -> { + bgColor = res.getColor(R.attr.cardBgColor) + fgColor = res.getColor(R.attr.mediumContrastTextColor) + setShadowAlpha(0x00) + } + else -> { + bgColor = res.getColor(R.attr.cardBgColor) + fgColor = res.getColor(R.attr.mediumContrastTextColor) + setShadowAlpha(0x00) + } + } + ring.percentage = percentage + ring.color = fgColor + ring.setBackgroundColor(bgColor) + ring.setText(text) + label.text = name + label.setTextColor(fgColor) + requestLayout() + postInvalidate() + } + + private val text: String + get() = if (isNumerical) { + (entryValue / 1000.0).toShortString() + } else when (entryState) { + YES_MANUAL, YES_AUTO -> resources.getString(R.string.fa_check) + SKIP -> resources.getString(R.string.fa_skipped) + UNKNOWN -> { + run { + if (preferences!!.areQuestionMarksEnabled()) { + return resources.getString(R.string.fa_question) + } else { + resources.getString(R.string.fa_times) + } + } + resources.getString(R.string.fa_times) + } + NO -> resources.getString(R.string.fa_times) + else -> resources.getString(R.string.fa_times) + } + + override val innerLayoutId: Int + get() = R.layout.widget_checkmark + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + var widthMeasureSpec = widthMeasureSpec + var heightMeasureSpec = heightMeasureSpec + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = MeasureSpec.getSize(heightMeasureSpec) + var w = width.toFloat() + var h = width * 1.25f + val scale = min(width / w, height / h) + w *= scale + h *= scale + if (h < getDimension(context, R.dimen.checkmarkWidget_heightBreakpoint)) ring.visibility = + GONE else ring.visibility = VISIBLE + widthMeasureSpec = MeasureSpec.makeMeasureSpec(w.toInt(), MeasureSpec.EXACTLY) + heightMeasureSpec = MeasureSpec.makeMeasureSpec(h.toInt(), MeasureSpec.EXACTLY) + var textSize = 0.15f * h + val maxTextSize = getDimension(context, R.dimen.smallerTextSize) + textSize = min(textSize, maxTextSize) + label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) + ring.setTextSize(textSize) + ring.setThickness(0.15f * textSize) + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + + private fun init() { + val appComponent: HabitsApplicationComponent = (context.applicationContext as HabitsApplication).component + preferences = appComponent.preferences + ring = findViewById(R.id.scoreRing) as RingView + label = findViewById(R.id.label) as TextView + ring.setIsTransparencyEnabled(true) + if (isInEditMode) { + percentage = 0.75f + name = "Wake up early" + activeColor = getAndroidTestColor(6) + entryValue = YES_MANUAL + refresh() + } + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.java deleted file mode 100644 index 83ff943fd..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.views; - -import android.content.Context; -import androidx.annotation.NonNull; -import android.widget.TextView; - -import org.isoron.uhabits.R; - -public class EmptyWidgetView extends HabitWidgetView -{ - - private TextView title; - - public EmptyWidgetView(Context context) - { - super(context); - init(); - } - - public void setTitle(String text) - { - title.setText(text); - } - - @Override - @NonNull - protected Integer getInnerLayoutId() - { - return R.layout.widget_graph; - } - - private void init() - { - title = (TextView) findViewById(R.id.title); - title.setVisibility(VISIBLE); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.kt new file mode 100644 index 000000000..814302fc8 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/EmptyWidgetView.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016-2021 Á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.views + +import android.content.Context +import android.view.View +import android.widget.TextView +import org.isoron.uhabits.R + +class EmptyWidgetView(context: Context?) : HabitWidgetView(context) { + private lateinit var title: TextView + fun setTitle(text: String?) { + title.text = text + } + + override val innerLayoutId: Int + get() = R.layout.widget_graph + + private fun init() { + title = findViewById(R.id.title) as TextView + title.visibility = VISIBLE + } + + init { + init() + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java deleted file mode 100644 index b574d9b56..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.views; - -import android.content.*; -import android.view.*; -import android.widget.*; - -import androidx.annotation.NonNull; - -import org.isoron.uhabits.*; - -public class GraphWidgetView extends HabitWidgetView -{ - - private final View dataView; - - private TextView title; - - public GraphWidgetView(Context context, View dataView) - { - super(context); - this.dataView = dataView; - init(); - } - - public View getDataView() - { - return dataView; - } - - public void setTitle(String text) - { - title.setText(text); - } - - @Override - @NonNull - protected Integer getInnerLayoutId() - { - return R.layout.widget_graph; - } - - private void init() - { - ViewGroup.LayoutParams params = - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT); - dataView.setLayoutParams(params); - - ViewGroup innerFrame = (ViewGroup) findViewById(R.id.innerFrame); - innerFrame.addView(dataView); - - title = (TextView) findViewById(R.id.title); - title.setVisibility(VISIBLE); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.kt new file mode 100644 index 000000000..0f935bdf1 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/GraphWidgetView.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016-2021 Á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.views + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import org.isoron.uhabits.R + +class GraphWidgetView(context: Context?, val dataView: View) : HabitWidgetView(context) { + private lateinit var title: TextView + fun setTitle(text: String?) { + title.text = text + } + + override val innerLayoutId: Int + get() = R.layout.widget_graph + + private fun init() { + val params = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + dataView.layoutParams = params + val innerFrame = findViewById(R.id.innerFrame) as ViewGroup + innerFrame.addView(dataView) + title = findViewById(R.id.title) as TextView + title.visibility = VISIBLE + } + + init { + init() + } +} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java deleted file mode 100644 index 96f0285fc..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.views; - -import android.content.*; -import android.graphics.*; -import android.graphics.drawable.*; -import android.graphics.drawable.shapes.*; -import android.util.*; -import android.view.*; -import android.widget.*; - -import androidx.annotation.*; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.utils.*; - -import java.util.*; - -import static org.isoron.uhabits.utils.InterfaceUtils.*; - -public abstract class HabitWidgetView extends FrameLayout -{ - @Nullable - protected InsetDrawable background; - - @Nullable - protected Paint backgroundPaint; - - protected ViewGroup frame; - - private int shadowAlpha; - - private StyledResources res; - - private int backgroundAlpha; - - public HabitWidgetView(Context context) - { - super(context); - init(); - } - - public HabitWidgetView(Context context, AttributeSet attrs) - { - super(context, attrs); - init(); - } - - public void setShadowAlpha(int shadowAlpha) - { - this.shadowAlpha = shadowAlpha; - rebuildBackground(); - } - - public void setBackgroundAlpha(int backgroundAlpha) - { - this.backgroundAlpha = backgroundAlpha; - rebuildBackground(); - } - - protected abstract - @NonNull - Integer getInnerLayoutId(); - - public void rebuildBackground() - { - Context context = getContext(); - - int shadowRadius = (int) dpToPixels(context, 2); - int shadowOffset = (int) dpToPixels(context, 1); - int shadowColor = Color.argb(shadowAlpha, 0, 0, 0); - - float cornerRadius = dpToPixels(context, 5); - float[] radii = new float[8]; - Arrays.fill(radii, cornerRadius); - - RoundRectShape shape = new RoundRectShape(radii, null, null); - ShapeDrawable innerDrawable = new ShapeDrawable(shape); - - int insetLeftTop = Math.max(shadowRadius - shadowOffset, 0); - int insetRightBottom = shadowRadius + shadowOffset; - - background = - new InsetDrawable(innerDrawable, insetLeftTop, insetLeftTop, - insetRightBottom, insetRightBottom); - backgroundPaint = innerDrawable.getPaint(); - backgroundPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, - shadowColor); - backgroundPaint.setColor(res.getColor(R.attr.cardBgColor)); - backgroundPaint.setAlpha(backgroundAlpha); - - frame = (ViewGroup) findViewById(R.id.frame); - if (frame != null) frame.setBackground(background); - } - - private void init() - { - inflate(getContext(), getInnerLayoutId(), this); - res = new StyledResources(getContext()); - shadowAlpha = (int) (255 * res.getFloat(R.attr.widgetShadowAlpha)); - rebuildBackground(); - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.kt new file mode 100644 index 000000000..85a219479 --- /dev/null +++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/HabitWidgetView.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2016-2021 Á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.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.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import org.isoron.uhabits.R +import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels +import org.isoron.uhabits.utils.StyledResources +import java.util.Arrays +import kotlin.math.max + +abstract class HabitWidgetView : FrameLayout { + protected var background: InsetDrawable? = null + protected var backgroundPaint: Paint? = null + protected var frame: ViewGroup? = null + private var shadowAlpha = 0 + private var res: StyledResources? = null + private var backgroundAlpha = 0 + + constructor(context: Context?) : super(context!!) { + init() + } + + constructor(context: Context?, attrs: AttributeSet?) : super( + context!!, + attrs + ) { + init() + } + + fun setShadowAlpha(shadowAlpha: Int) { + this.shadowAlpha = shadowAlpha + rebuildBackground() + } + + fun setBackgroundAlpha(backgroundAlpha: Int) { + this.backgroundAlpha = backgroundAlpha + rebuildBackground() + } + + protected abstract val innerLayoutId: Int + fun rebuildBackground() { + val context = context + val shadowRadius = dpToPixels(context, 2f).toInt() + val shadowOffset = dpToPixels(context, 1f).toInt() + val shadowColor = Color.argb(shadowAlpha, 0, 0, 0) + val cornerRadius = dpToPixels(context, 5f) + val radii = FloatArray(8) + Arrays.fill(radii, cornerRadius) + val shape = RoundRectShape(radii, null, null) + val innerDrawable = ShapeDrawable(shape) + val insetLeftTop = max(shadowRadius - shadowOffset, 0) + val insetRightBottom = shadowRadius + shadowOffset + background = InsetDrawable( + innerDrawable, + insetLeftTop, + insetLeftTop, + insetRightBottom, + insetRightBottom + ) + backgroundPaint = innerDrawable.paint + backgroundPaint?.setShadowLayer( + shadowRadius.toFloat(), + shadowOffset.toFloat(), + shadowOffset.toFloat(), + shadowColor + ) + backgroundPaint?.color = res!!.getColor(R.attr.cardBgColor) + backgroundPaint?.alpha = backgroundAlpha + frame = findViewById(R.id.frame) as ViewGroup + if (frame != null) frame!!.background = background + } + + private fun init() { + inflate(context, innerLayoutId, this) + res = StyledResources(context) + shadowAlpha = (255 * res!!.getFloat(R.attr.widgetShadowAlpha)).toInt() + rebuildBackground() + } +} diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.java deleted file mode 100644 index ac41f26a3..000000000 --- a/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.commands.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.models.memory.*; -import org.isoron.uhabits.core.tasks.*; -import org.isoron.uhabits.core.test.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; -import org.junit.runner.*; -import org.mockito.junit.*; - -import java.util.*; - -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class BaseAndroidJVMTest -{ - protected HabitList habitList; - protected HabitFixtures fixtures; - protected MemoryModelFactory modelFactory; - protected SingleThreadTaskRunner taskRunner; - protected CommandRunner commandRunner; - - @Before - public void setUp() - { - long fixed_local_time = 1422172800000L; - DateUtils.setFixedLocalTime(fixed_local_time); - DateUtils.setStartDayOffset(0, 0); - modelFactory = new MemoryModelFactory(); - habitList = spy(modelFactory.buildHabitList()); - fixtures = new HabitFixtures(modelFactory, habitList); - taskRunner = new SingleThreadTaskRunner(); - commandRunner = new CommandRunner(taskRunner); - } - - @After - public void tearDown() - { - DateUtils.setFixedLocalTime(null); - DateUtils.setStartDayOffset(0, 0); - } - - public long timestamp(int year, int month, int day) - { - GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); - cal.set(year, month, day); - return cal.getTimeInMillis(); - } - - @Test - public void nothing() - { - - } -} diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.kt b/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.kt new file mode 100644 index 000000000..a6a1e5021 --- /dev/null +++ b/uhabits-android/src/test/java/org/isoron/uhabits/BaseAndroidJVMTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016-2021 Á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 com.nhaarman.mockitokotlin2.spy +import org.isoron.uhabits.core.commands.CommandRunner +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.memory.MemoryModelFactory +import org.isoron.uhabits.core.tasks.SingleThreadTaskRunner +import org.isoron.uhabits.core.test.HabitFixtures +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedLocalTime +import org.isoron.uhabits.core.utils.DateUtils.Companion.setStartDayOffset +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnitRunner + +@RunWith(MockitoJUnitRunner::class) +open class BaseAndroidJVMTest { + private lateinit var habitList: HabitList + protected lateinit var fixtures: HabitFixtures + private lateinit var modelFactory: MemoryModelFactory + private lateinit var taskRunner: SingleThreadTaskRunner + private lateinit var commandRunner: CommandRunner + + @Before + open fun setUp() { + val fixedLocalTime = 1422172800000L + setFixedLocalTime(fixedLocalTime) + setStartDayOffset(0, 0) + modelFactory = MemoryModelFactory() + habitList = spy(modelFactory.buildHabitList()) + fixtures = HabitFixtures(modelFactory, habitList) + taskRunner = SingleThreadTaskRunner() + commandRunner = CommandRunner(taskRunner) + } + + @After + fun tearDown() { + setFixedLocalTime(null) + setStartDayOffset(0, 0) + } + + @Test + fun nothing() { + } +} diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java deleted file mode 100644 index f7a384392..000000000 --- a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.receivers; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.reminders.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import static org.mockito.Mockito.*; - -public class ReminderControllerTest extends BaseAndroidJVMTest -{ - - private ReminderController controller; - - private ReminderScheduler reminderScheduler; - - private NotificationTray notificationTray; - - private Preferences preferences; - - @Override - public void setUp() - { - super.setUp(); - - reminderScheduler = mock(ReminderScheduler.class); - notificationTray = mock(NotificationTray.class); - preferences = mock(Preferences.class); - - controller = new ReminderController(reminderScheduler, - notificationTray, preferences); - } - - @Test - public void testOnDismiss() throws Exception - { - verifyNoMoreInteractions(reminderScheduler); - verifyNoMoreInteractions(notificationTray); - verifyNoMoreInteractions(preferences); - } - - @Test - public void testOnShowReminder() throws Exception - { - Habit habit = mock(Habit.class); - controller.onShowReminder(habit, Timestamp.ZERO.plus(100), 456); - verify(notificationTray).show(habit, Timestamp.ZERO.plus(100), 456); - verify(reminderScheduler).scheduleAll(); - } - - @Test - public void testOnBootCompleted() throws Exception - { - controller.onBootCompleted(); - verify(reminderScheduler).scheduleAll(); - } -} \ No newline at end of file diff --git a/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.kt b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.kt new file mode 100644 index 000000000..a855a9018 --- /dev/null +++ b/uhabits-android/src/test/java/org/isoron/uhabits/receivers/ReminderControllerTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016-2021 Á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.receivers + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions +import org.isoron.uhabits.BaseAndroidJVMTest +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.reminders.ReminderScheduler +import org.isoron.uhabits.core.ui.NotificationTray +import org.junit.Test + +class ReminderControllerTest : BaseAndroidJVMTest() { + private lateinit var controller: ReminderController + private lateinit var reminderScheduler: ReminderScheduler + private lateinit var notificationTray: NotificationTray + private lateinit var preferences: Preferences + override fun setUp() { + super.setUp() + reminderScheduler = mock() + notificationTray = mock() + preferences = mock() + controller = ReminderController( + reminderScheduler, + notificationTray, + preferences + ) + } + + @Test + @Throws(Exception::class) + fun testOnDismiss() { + verifyNoMoreInteractions(reminderScheduler) + verifyNoMoreInteractions(notificationTray) + verifyNoMoreInteractions(preferences) + } + + @Test + @Throws(Exception::class) + fun testOnShowReminder() { + val habit: Habit = mock() + controller.onShowReminder(habit, Timestamp.ZERO.plus(100), 456) + verify(notificationTray).show(habit, Timestamp.ZERO.plus(100), 456) + verify(reminderScheduler).scheduleAll() + } + + @Test + @Throws(Exception::class) + fun testOnBootCompleted() { + controller.onBootCompleted() + verify(reminderScheduler).scheduleAll() + } +} diff --git a/uhabits-core-legacy/src/test/common/org/isoron/uhabits/components/NumberButtonTest.kt b/uhabits-core-legacy/src/test/common/org/isoron/uhabits/components/NumberButtonTest.kt index c528f2d9d..7f65a95ac 100644 --- a/uhabits-core-legacy/src/test/common/org/isoron/uhabits/components/NumberButtonTest.kt +++ b/uhabits-core-legacy/src/test/common/org/isoron/uhabits/components/NumberButtonTest.kt @@ -55,10 +55,4 @@ class NumberButtonTest : BaseViewTest() { val btn = NumberButton(theme.color(8), 99.0, 100.0, "steps", theme) assertRenders(48, 48, "$base/render_below.png", btn) } - - //@Test - fun testRenderZero() = asyncTest { - val btn = NumberButton(theme.color(8), 0.0, 100.0, "steps", theme) - assertRenders(48, 48, "$base/render_zero.png", btn) - } -} \ No newline at end of file +} diff --git a/uhabits-core/build.gradle b/uhabits-core/build.gradle index b26d4489c..2a577424a 100644 --- a/uhabits-core/build.gradle +++ b/uhabits-core/build.gradle @@ -74,6 +74,7 @@ kotlin { implementation 'nl.jqno.equalsverifier:equalsverifier:2.4.8' implementation 'org.apache.commons:commons-io:1.3.2' implementation 'org.mockito:mockito-core:2.28.2' + implementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" } } } diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/gui/Canvas.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/gui/Canvas.kt index 4cfec5834..faab51b87 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/gui/Canvas.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/gui/Canvas.kt @@ -51,7 +51,7 @@ interface Canvas { fun fillCircle(centerX: Double, centerY: Double, radius: Double) fun setTextAlign(align: TextAlign) fun toImage(): Image - fun measureText(test: String): Double + fun measureText(text: String): Double /** * Fills entire canvas with the current color. diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/gui/FontAwesome.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/gui/FontAwesome.kt index 15e3173ab..d9fb92130 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/gui/FontAwesome.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/gui/FontAwesome.kt @@ -21,7 +21,7 @@ package org.isoron.platform.gui class FontAwesome { companion object { - val CHECK = "\uf00c" - val TIMES = "\uf00d" + const val CHECK = "\uf00c" + const val TIMES = "\uf00d" } } diff --git a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt index d8ff099ec..5d440be90 100644 --- a/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt +++ b/uhabits-core/src/commonMain/kotlin/org/isoron/platform/time/Dates.kt @@ -153,10 +153,10 @@ private fun daysSince2000(year: Int, month: Int, day: Int): Int { result += ceil((year - 2000) / 4.0).toInt() result -= ceil((year - 2000) / 100.0).toInt() result += ceil((year - 2000) / 400.0).toInt() - if (isLeapYear(year)) { - result += leapOffset[month - 1] + result += if (isLeapYear(year)) { + leapOffset[month - 1] } else { - result += nonLeapOffset[month - 1] + nonLeapOffset[month - 1] } result += (day - 1) return result diff --git a/uhabits-core/src/jvmMain/java/org/isoron/platform/gui/JavaCanvas.kt b/uhabits-core/src/jvmMain/java/org/isoron/platform/gui/JavaCanvas.kt index 21bd5c69d..36a8a1393 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/platform/gui/JavaCanvas.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/platform/gui/JavaCanvas.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.runBlocking import org.isoron.platform.io.JavaFileOpener import org.isoron.platform.io.JavaResourceFile import java.awt.BasicStroke +import java.awt.Graphics2D import java.awt.RenderingHints.KEY_ANTIALIASING import java.awt.RenderingHints.KEY_FRACTIONALMETRICS import java.awt.RenderingHints.KEY_TEXT_ANTIALIASING @@ -54,7 +55,7 @@ class JavaCanvas( private var textAlign = TextAlign.CENTER val widthPx = image.width val heightPx = image.height - val g2d = image.createGraphics() + val g2d: Graphics2D = image.createGraphics() private val NOTO_REGULAR_FONT = createFont("fonts/NotoSans-Regular.ttf") private val NOTO_BOLD_FONT = createFont("fonts/NotoSans-Bold.ttf") @@ -96,24 +97,28 @@ class JavaCanvas( val bx = bounds.x.roundToInt() val by = bounds.y.roundToInt() - if (textAlign == TextAlign.CENTER) { - g2d.drawString( - text, - toPixel(x) - bx - bWidth / 2, - toPixel(y) - by - bHeight / 2 - ) - } else if (textAlign == TextAlign.LEFT) { - g2d.drawString( - text, - toPixel(x) - bx, - toPixel(y) - by - bHeight / 2 - ) - } else { - g2d.drawString( - text, - toPixel(x) - bx - bWidth, - toPixel(y) - by - bHeight / 2 - ) + when (textAlign) { + TextAlign.CENTER -> { + g2d.drawString( + text, + toPixel(x) - bx - bWidth / 2, + toPixel(y) - by - bHeight / 2 + ) + } + TextAlign.LEFT -> { + g2d.drawString( + text, + toPixel(x) - bx, + toPixel(y) - by - bHeight / 2 + ) + } + else -> { + g2d.drawString( + text, + toPixel(x) - bx - bWidth, + toPixel(y) - by - bHeight / 2 + ) + } } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaFiles.kt b/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaFiles.kt index d00e7ffef..e2a7a3e46 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaFiles.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/platform/io/JavaFiles.kt @@ -33,8 +33,8 @@ class JavaResourceFile(val path: String) : ResourceFile { get() { val mainPath = Paths.get("assets/main/$path") val testPath = Paths.get("assets/test/$path") - if (Files.exists(mainPath)) return mainPath - else return testPath + return if (Files.exists(mainPath)) mainPath + else testPath } override suspend fun exists(): Boolean { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/database/Database.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/database/Database.kt index ea0bdf6ab..e3593868a 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/database/Database.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/database/Database.kt @@ -56,7 +56,7 @@ interface Database { val file: File? - interface ProcessCallback { + fun interface ProcessCallback { fun process(cursor: Cursor) } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/database/SQLParser.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/database/SQLParser.kt index e5fa89156..40320feb0 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/database/SQLParser.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/database/SQLParser.kt @@ -71,7 +71,7 @@ object SQLParser { val buffer = BufferedInputStream(stream) val commands: MutableList = ArrayList() val sb = StringBuffer() - try { + buffer.use { buffer -> val tokenizer = Tokenizer(buffer) var state = STATE_NONE while (tokenizer.hasNext()) { @@ -104,7 +104,7 @@ object SQLParser { } if (state == STATE_NONE || state == STATE_STRING) { if (state == STATE_NONE && isWhitespace(c)) { - if (sb.length > 0 && sb[sb.length - 1] != ' ') { + if (sb.isNotEmpty() && sb[sb.length - 1] != ' ') { sb.append(' ') } } else { @@ -112,8 +112,6 @@ object SQLParser { } } } - } finally { - buffer.close() } if (sb.isNotEmpty()) { commands.add(sb.toString().trim { it <= ' ' }) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/HabitsCSVExporter.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/HabitsCSVExporter.kt index 2389da845..3126b18a3 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/HabitsCSVExporter.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/HabitsCSVExporter.kt @@ -37,6 +37,7 @@ import java.util.LinkedList import java.util.Locale import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream +import kotlin.math.min /** * Class that exports the application data to CSV files. @@ -77,7 +78,7 @@ class HabitsCSVExporter( private fun sanitizeFilename(name: String): String { val s = name.replace("[^ a-zA-Z0-9\\._-]+".toRegex(), "") - return s.substring(0, Math.min(s.length, 100)) + return s.substring(0, min(s.length, 100)) } private fun writeHabits() { diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/LoopDBImporter.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/LoopDBImporter.kt index fecd02c1a..c8f76e4a5 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/LoopDBImporter.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/LoopDBImporter.kt @@ -53,7 +53,7 @@ class LoopDBImporter override fun canHandle(file: File): Boolean { if (!file.isSQLite3File()) return false - val db = opener.open(file)!! + val db = opener.open(file) var canHandle = true val c = db.query("select count(*) from SQLITE_MASTER where name='Habits' or name='Repetitions'") if (!c.moveToNext() || c.getInt(0) != 2) { @@ -70,7 +70,7 @@ class LoopDBImporter } override fun importHabitsFromFile(file: File) { - val db = opener.open(file)!! + val db = opener.open(file) val helper = MigrationHelper(db) helper.migrateTo(DATABASE_VERSION) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt index 3e77aa2bf..76a2fabf8 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/EntryList.kt @@ -156,15 +156,15 @@ open class EntryList { get() = begin.daysUntil(end) + 1 } - /** - * Converts a list of intervals into a list of entries. Entries that fall outside of any - * interval receive value UNKNOWN. Entries that fall within an interval but do not appear - * in [original] receive value YES_AUTO. Entries provided in [original] are copied over. - * - * The intervals should be sorted by timestamp. The first element in the list should - * correspond to the newest interval. - */ companion object { + /** + * Converts a list of intervals into a list of entries. Entries that fall outside of any + * interval receive value UNKNOWN. Entries that fall within an interval but do not appear + * in [original] receive value YES_AUTO. Entries provided in [original] are copied over. + * + * The intervals should be sorted by timestamp. The first element in the list should + * correspond to the newest interval. + */ fun buildEntriesFromInterval( original: List, intervals: List, diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ModelFactory.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ModelFactory.kt index a55dc1242..2edeeed51 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ModelFactory.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ModelFactory.kt @@ -31,13 +31,12 @@ interface ModelFactory { fun buildHabit(): Habit { val scores = buildScoreList() val streaks = buildStreakList() - val habit = Habit( + return Habit( scores = scores, streaks = streaks, originalEntries = buildOriginalEntries(), computedEntries = buildComputedEntries(), ) - return habit } fun buildComputedEntries(): EntryList fun buildOriginalEntries(): EntryList diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Score.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Score.kt index dc2fd3fa8..4b7bc5730 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Score.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Score.kt @@ -18,6 +18,7 @@ */ package org.isoron.uhabits.core.models +import kotlin.math.pow import kotlin.math.sqrt data class Score( @@ -40,7 +41,7 @@ data class Score( previousScore: Double, checkmarkValue: Double, ): Double { - val multiplier = Math.pow(0.5, sqrt(frequency) / 13.0) + val multiplier = 0.5.pow(sqrt(frequency) / 13.0) var score = previousScore * multiplier score += checkmarkValue * (1 - multiplier) return score diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt index 6e399a2cf..11a2b146c 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/ScoreList.kt @@ -109,7 +109,7 @@ class ScoreList { } } if (values[offset] != Entry.SKIP) { - val percentageCompleted = Math.min(1.0, rollingSum / numerator) + val percentageCompleted = min(1.0, rollingSum / numerator) previousValue = compute(freq, previousValue, percentageCompleted) } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.java b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.java deleted file mode 100644 index a8fbf2aed..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models; - -import org.apache.commons.lang3.builder.*; - -import java.util.*; - -import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle; - -public final class WeekdayList -{ - public static final WeekdayList EVERY_DAY = new WeekdayList(127); - - private final boolean[] weekdays; - - public WeekdayList(int packedList) - { - weekdays = new boolean[7]; - - int current = 1; - for (int i = 0; i < 7; i++) - { - if ((packedList & current) != 0) weekdays[i] = true; - current = current << 1; - } - } - - public WeekdayList(boolean weekdays[]) - { - this.weekdays = Arrays.copyOf(weekdays, 7); - } - - public boolean isEmpty() - { - for (boolean d : weekdays) if (d) return false; - return true; - } - - public boolean[] toArray() - { - return Arrays.copyOf(weekdays, 7); - } - - public int toInteger() - { - int packedList = 0; - int current = 1; - - for (int i = 0; i < 7; i++) - { - if (weekdays[i]) packedList |= current; - current = current << 1; - } - - return packedList; - } - - @Override - public boolean equals(Object o) - { - if (this == o) return true; - - if (o == null || getClass() != o.getClass()) return false; - - WeekdayList that = (WeekdayList) o; - - return new EqualsBuilder().append(weekdays, that.weekdays).isEquals(); - } - - @Override - public int hashCode() - { - return new HashCodeBuilder(17, 37).append(weekdays).toHashCode(); - } - - @Override - public String toString() - { - return new ToStringBuilder(this, defaultToStringStyle()) - .append("weekdays", weekdays) - .toString(); - } -} diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.kt new file mode 100644 index 000000000..f104cb32b --- /dev/null +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/WeekdayList.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models + +import org.apache.commons.lang3.builder.EqualsBuilder +import org.apache.commons.lang3.builder.HashCodeBuilder +import org.apache.commons.lang3.builder.ToStringBuilder +import org.isoron.uhabits.core.utils.StringUtils.Companion.defaultToStringStyle +import java.util.Arrays + +class WeekdayList { + private val weekdays: BooleanArray + + constructor(packedList: Int) { + weekdays = BooleanArray(7) + var current = 1 + for (i in 0..6) { + if (packedList and current != 0) weekdays[i] = true + current = current shl 1 + } + } + + constructor(weekdays: BooleanArray?) { + this.weekdays = Arrays.copyOf(weekdays, 7) + } + + val isEmpty: Boolean + get() { + for (d in weekdays) if (d) return false + return true + } + + fun toArray(): BooleanArray { + return weekdays.copyOf(7) + } + + fun toInteger(): Int { + var packedList = 0 + var current = 1 + for (i in 0..6) { + if (weekdays[i]) packedList = packedList or current + current = current shl 1 + } + return packedList + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + val that = other as WeekdayList + return EqualsBuilder().append(weekdays, that.weekdays).isEquals + } + + override fun hashCode(): Int { + return HashCodeBuilder(17, 37).append(weekdays).toHashCode() + } + + override fun toString(): String { + return ToStringBuilder(this, defaultToStringStyle()) + .append("weekdays", weekdays) + .toString() + } + + companion object { + val EVERY_DAY = WeekdayList(127) + } +} diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.java b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.java deleted file mode 100644 index 512642761..000000000 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.test; - -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.models.sqlite.*; -import org.isoron.uhabits.core.utils.*; - -import static org.isoron.uhabits.core.models.Entry.*; - -public class HabitFixtures -{ - public boolean NON_DAILY_HABIT_CHECKS[] = { - true, false, false, true, true, true, false, false, true, true - }; - - private final ModelFactory modelFactory; - - private HabitList habitList; - - public HabitFixtures(ModelFactory modelFactory, HabitList habitList) - { - this.modelFactory = modelFactory; - this.habitList = habitList; - } - - public Habit createEmptyHabit() - { - Habit habit = modelFactory.buildHabit(); - habit.setName("Meditate"); - habit.setQuestion("Did you meditate this morning?"); - habit.setColor(new PaletteColor(3)); - habit.setFrequency(Frequency.DAILY); - saveIfSQLite(habit); - - return habit; - } - - public Habit createLongHabit() - { - Habit habit = createEmptyHabit(); - habit.setFrequency(new Frequency(3, 7)); - habit.setColor(new PaletteColor(4)); - - Timestamp today = DateUtils.getToday(); - 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.getOriginalEntries().add(new Entry(today.minus(mark), YES_MANUAL)); - - habit.recompute(); - return habit; - } - - public Habit createNumericalHabit() - { - Habit habit = modelFactory.buildHabit(); - habit.setType(Habit.NUMBER_HABIT); - habit.setName("Run"); - habit.setQuestion("How many miles did you run today?"); - habit.setUnit("miles"); - habit.setTargetType(Habit.AT_LEAST); - habit.setTargetValue(2.0); - habit.setColor(new PaletteColor(1)); - saveIfSQLite(habit); - - Timestamp today = DateUtils.getToday(); - int times[] = {0, 1, 3, 5, 7, 8, 9, 10}; - int values[] = {100, 200, 300, 400, 500, 600, 700, 800}; - - for (int i = 0; i < times.length; i++) - { - Timestamp timestamp = today.minus(times[i]); - habit.getOriginalEntries().add(new Entry(timestamp, values[i])); - } - - habit.recompute(); - return habit; - } - - public Habit createLongNumericalHabit(Timestamp reference) - { - Habit habit = modelFactory.buildHabit(); - habit.setType(Habit.NUMBER_HABIT); - habit.setName("Walk"); - habit.setQuestion("How many steps did you walk today?"); - habit.setUnit("steps"); - habit.setTargetType(Habit.AT_LEAST); - habit.setTargetValue(100); - habit.setColor(new PaletteColor(1)); - saveIfSQLite(habit); - - int times[] = {0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78, - 83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164, - 166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295, - 302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367, - 372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449, - 455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507}; - - int values[] = {230, 306, 148, 281, 134, 285, 104, 158, 325, 236, 303, 210, 118, 124, - 301, 201, 156, 376, 347, 367, 396, 134, 160, 381, 155, 354, 231, 134, 164, 354, - 236, 398, 199, 221, 208, 397, 253, 276, 214, 341, 299, 221, 353, 250, 341, 168, - 374, 205, 182, 217, 297, 321, 104, 237, 294, 110, 136, 229, 102, 271, 250, 294, - 158, 319, 379, 126, 282, 155, 288, 159, 215, 247, 207, 226, 244, 158, 371, 219, - 272, 228, 350, 153, 356, 279, 394, 202, 213, 214, 112, 248, 139, 245, 165, 256, - 370, 187, 208, 231, 341, 312}; - - for (int i = 0; i < times.length; i++) - { - Timestamp timestamp = reference.minus(times[i]); - habit.getOriginalEntries().add(new Entry(timestamp, values[i])); - } - - habit.recompute(); - return habit; - } - - public Habit createShortHabit() - { - Habit habit = modelFactory.buildHabit(); - habit.setName("Wake up early"); - habit.setQuestion("Did you wake up before 6am?"); - habit.setFrequency(new Frequency(2, 3)); - saveIfSQLite(habit); - - Timestamp timestamp = DateUtils.getToday(); - for (boolean c : NON_DAILY_HABIT_CHECKS) - { - int value = NO; - if (c) value = YES_MANUAL; - habit.getOriginalEntries().add(new Entry(timestamp, value)); - timestamp = timestamp.minus(1); - } - - habit.recompute(); - return habit; - } - - private void saveIfSQLite(Habit habit) - { - if (!(habit.getOriginalEntries() instanceof SQLiteEntryList)) return; - habitList.add(habit); - } -} diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.kt new file mode 100644 index 000000000..86eaa122e --- /dev/null +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/test/HabitFixtures.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016-2021 Á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.core.test + +import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.models.Frequency +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.ModelFactory +import org.isoron.uhabits.core.models.PaletteColor +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.models.sqlite.SQLiteEntryList +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday + +class HabitFixtures(private val modelFactory: ModelFactory, private val habitList: HabitList) { + var NON_DAILY_HABIT_CHECKS = booleanArrayOf( + true, false, false, true, true, true, false, false, true, true + ) + + fun createEmptyHabit( + name: String = "Meditate", + color: PaletteColor = PaletteColor(3), + position: Int = 0 + ): Habit { + val habit = modelFactory.buildHabit() + habit.name = name + habit.question = "Did you meditate this morning?" + habit.color = color + habit.position = position + habit.frequency = Frequency.DAILY + saveIfSQLite(habit) + return habit + } + + fun createLongHabit(): Habit { + val habit = createEmptyHabit() + habit.frequency = Frequency(3, 7) + habit.color = PaletteColor(4) + val today = getToday() + val marks = intArrayOf( + 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 (mark in marks) habit.originalEntries.add(Entry(today.minus(mark), Entry.YES_MANUAL)) + habit.recompute() + return habit + } + + fun createNumericalHabit(): Habit { + val habit = modelFactory.buildHabit() + habit.type = Habit.NUMBER_HABIT + habit.name = "Run" + habit.question = "How many miles did you run today?" + habit.unit = "miles" + habit.targetType = Habit.AT_LEAST + habit.targetValue = 2.0 + habit.color = PaletteColor(1) + saveIfSQLite(habit) + val today = getToday() + val times = intArrayOf(0, 1, 3, 5, 7, 8, 9, 10) + val values = intArrayOf(100, 200, 300, 400, 500, 600, 700, 800) + for (i in times.indices) { + val timestamp = today.minus(times[i]) + habit.originalEntries.add(Entry(timestamp, values[i])) + } + habit.recompute() + return habit + } + + fun createLongNumericalHabit(reference: Timestamp): Habit { + val habit = modelFactory.buildHabit() + habit.type = Habit.NUMBER_HABIT + habit.name = "Walk" + habit.question = "How many steps did you walk today?" + habit.unit = "steps" + habit.targetType = Habit.AT_LEAST + habit.targetValue = 100.0 + habit.color = PaletteColor(1) + saveIfSQLite(habit) + val times = intArrayOf( + 0, 5, 9, 15, 17, 21, 23, 27, 28, 35, 41, 45, 47, 53, 56, 62, 70, 73, 78, + 83, 86, 94, 101, 106, 113, 114, 120, 126, 130, 133, 141, 143, 148, 151, 157, 164, + 166, 171, 173, 176, 179, 183, 191, 259, 264, 268, 270, 275, 282, 284, 289, 295, + 302, 306, 310, 315, 323, 325, 328, 335, 343, 349, 351, 353, 357, 359, 360, 367, + 372, 376, 380, 385, 393, 400, 404, 412, 415, 418, 422, 425, 433, 437, 444, 449, + 455, 460, 462, 465, 470, 471, 479, 481, 485, 489, 494, 495, 500, 501, 503, 507 + ) + val values = intArrayOf( + 230, 306, 148, 281, 134, 285, 104, 158, 325, 236, 303, 210, 118, 124, + 301, 201, 156, 376, 347, 367, 396, 134, 160, 381, 155, 354, 231, 134, 164, 354, + 236, 398, 199, 221, 208, 397, 253, 276, 214, 341, 299, 221, 353, 250, 341, 168, + 374, 205, 182, 217, 297, 321, 104, 237, 294, 110, 136, 229, 102, 271, 250, 294, + 158, 319, 379, 126, 282, 155, 288, 159, 215, 247, 207, 226, 244, 158, 371, 219, + 272, 228, 350, 153, 356, 279, 394, 202, 213, 214, 112, 248, 139, 245, 165, 256, + 370, 187, 208, 231, 341, 312 + ) + for (i in times.indices) { + val timestamp = reference.minus(times[i]) + habit.originalEntries.add(Entry(timestamp, values[i])) + } + habit.recompute() + return habit + } + + fun createShortHabit(): Habit { + val habit = modelFactory.buildHabit() + habit.name = "Wake up early" + habit.question = "Did you wake up before 6am?" + habit.frequency = Frequency(2, 3) + saveIfSQLite(habit) + var timestamp = getToday() + for (c in NON_DAILY_HABIT_CHECKS) { + var value = Entry.NO + if (c) value = Entry.YES_MANUAL + habit.originalEntries.add(Entry(timestamp, value)) + timestamp = timestamp.minus(1) + } + habit.recompute() + return habit + } + + private fun saveIfSQLite(habit: Habit) { + if (habit.originalEntries !is SQLiteEntryList) return + habitList.add(habit) + } +} diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt index 38dd68d28..49dc07af7 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenter.kt @@ -29,6 +29,8 @@ import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback import org.isoron.uhabits.core.utils.DateUtils import java.io.File import java.util.Random +import kotlin.math.max +import kotlin.math.min class ShowHabitMenuPresenter( private val commandRunner: CommandRunner, @@ -67,7 +69,7 @@ class ShowHabitMenuPresenter( habit.originalEntries.clear() var strength = 50.0 for (i in 0 until 365 * 5) { - if (i % 7 == 0) strength = Math.max(0.0, Math.min(100.0, strength + 10 * random.nextGaussian())) + if (i % 7 == 0) strength = max(0.0, min(100.0, strength + 10 * random.nextGaussian())) if (random.nextInt(100) > strength) continue var value = Entry.YES_MANUAL if (habit.isNumerical) value = (1000 + 250 * random.nextGaussian() * strength / 100).toInt() * 1000 diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/OverviewCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/OverviewCard.kt index a999a2689..3cd66fad4 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/OverviewCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/OverviewCard.kt @@ -39,9 +39,9 @@ class OverviewCardPresenter { val lastMonth = today.minus(30) val lastYear = today.minus(365) val scores = habit.scores - val scoreToday = scores.get(today).value.toFloat() - val scoreLastMonth = scores.get(lastMonth).value.toFloat() - val scoreLastYear = scores.get(lastYear).value.toFloat() + val scoreToday = scores[today].value.toFloat() + val scoreLastMonth = scores[lastMonth].value.toFloat() + val scoreLastYear = scores[lastYear].value.toFloat() val totalCount = habit.originalEntries.getKnown() .filter { it.value == Entry.YES_MANUAL } .count() diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/ScoreCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/ScoreCard.kt index 823b5a042..4d487a103 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/ScoreCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/ScoreCard.kt @@ -39,13 +39,13 @@ class ScoreCardPresenter( companion object { val BUCKET_SIZES = intArrayOf(1, 7, 31, 92, 365) fun getTruncateField(bucketSize: Int): DateUtils.TruncateField { - when (bucketSize) { - 1 -> return DateUtils.TruncateField.DAY - 7 -> return DateUtils.TruncateField.WEEK_NUMBER - 31 -> return DateUtils.TruncateField.MONTH - 92 -> return DateUtils.TruncateField.QUARTER - 365 -> return DateUtils.TruncateField.YEAR - else -> return DateUtils.TruncateField.MONTH + return when (bucketSize) { + 1 -> DateUtils.TruncateField.DAY + 7 -> DateUtils.TruncateField.WEEK_NUMBER + 31 -> DateUtils.TruncateField.MONTH + 92 -> DateUtils.TruncateField.QUARTER + 365 -> DateUtils.TruncateField.YEAR + else -> DateUtils.TruncateField.MONTH } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/BarChart.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/BarChart.kt index b933de6ee..ca8e700ad 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/BarChart.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/BarChart.kt @@ -63,7 +63,7 @@ class BarChart( val nColumns = floor((safeWidth) / barGroupWidth).toInt() val marginLeft = (safeWidth - nColumns * barGroupWidth) / 2 val maxBarHeight = height - footerHeight - paddingTop - var maxValue = series.map { it.max()!! }.max()!! + var maxValue = series.map { it.maxOrNull()!! }.maxOrNull()!! maxValue = max(maxValue, 1.0) canvas.setColor(theme.cardBackgroundColor) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/HistoryChart.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/HistoryChart.kt index c1dcc2584..699cc2ee6 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/HistoryChart.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/HistoryChart.kt @@ -192,15 +192,15 @@ class HistoryChart( val value = if (offset >= series.size) Square.OFF else series[offset] val squareColor: Color val color = theme.color(paletteColor.paletteIndex) - when (value) { + squareColor = when (value) { Square.ON -> { - squareColor = color + color } Square.OFF -> { - squareColor = theme.lowContrastTextColor + theme.lowContrastTextColor } Square.DIMMED, Square.HATCHED -> { - squareColor = color.blendWith(theme.cardBackgroundColor, 0.5) + color.blendWith(theme.cardBackgroundColor, 0.5) } } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt index 7b388ecbc..fd7929b87 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/DateUtils.kt @@ -76,7 +76,7 @@ abstract class DateUtils { if (fixedLocalTime != null) return fixedLocalTime as Long val tz = getTimeZone() - val now = Date().getTime() + val now = Date().time return now + tz.getOffset(now) } diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt index 4d2277563..e0661ba77 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/MidnightTimer.kt @@ -37,7 +37,7 @@ open class MidnightTimer @Inject constructor() { this.listeners.add(listener) } - @Synchronized fun onPause() = executor.shutdownNow() + @Synchronized fun onPause(): MutableList? = executor.shutdownNow() @Synchronized fun onResume() { executor = Executors.newSingleThreadScheduledExecutor() diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/StringUtils.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/StringUtils.kt index 6933fb1d0..a65cf4241 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/StringUtils.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/utils/StringUtils.kt @@ -59,11 +59,10 @@ class StringUtils { @JvmStatic fun splitLongs(str: String): LongArray { val parts: Array = org.apache.commons.lang3.StringUtils.split(str, ',') - val numbers = LongArray(parts.size) { + return LongArray(parts.size) { i -> parts[i].toLong() } - return numbers } } } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.java deleted file mode 100644 index 3447ad6b5..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core; - -import androidx.annotation.*; - -import org.apache.commons.io.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.database.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.models.Timestamp; -import org.isoron.uhabits.core.models.memory.*; -import org.isoron.uhabits.core.tasks.*; -import org.isoron.uhabits.core.test.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; -import org.junit.runner.*; -import org.mockito.junit.*; - -import java.io.*; -import java.nio.file.Paths; -import java.sql.*; -import java.util.*; - -import static org.isoron.uhabits.core.ConstantsKt.*; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class BaseUnitTest -{ - - // 8:00am, January 25th, 2015 (UTC) - protected static final long FIXED_LOCAL_TIME = 1422172800000L; - - protected HabitList habitList; - - protected HabitFixtures fixtures; - - protected ModelFactory modelFactory; - - protected SingleThreadTaskRunner taskRunner; - - protected CommandRunner commandRunner; - - protected DatabaseOpener databaseOpener = new DatabaseOpener() - { - @Override - public Database open(@NonNull File file) - { - try - { - return new JdbcDatabase(DriverManager.getConnection( - String.format("jdbc:sqlite:%s", file.getAbsolutePath()))); - } - catch (SQLException e) - { - throw new RuntimeException(e); - } - } - }; - - @Before - public void setUp() throws Exception - { - DateUtils.setFixedLocalTime(FIXED_LOCAL_TIME); - DateUtils.setStartDayOffset(0, 0); - - modelFactory = new MemoryModelFactory(); - habitList = spy(modelFactory.buildHabitList()); - fixtures = new HabitFixtures(modelFactory, habitList); - taskRunner = new SingleThreadTaskRunner(); - commandRunner = new CommandRunner(taskRunner); - } - - @After - public void tearDown() throws Exception - { - validateMockitoUsage(); - DateUtils.setFixedLocalTime(null); - DateUtils.setStartDayOffset(0, 0); - } - - public long unixTime(int year, int month, int day) - { - GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); - cal.set(year, month, day, 0, 0, 0); - return cal.getTimeInMillis(); - } - - public long unixTime(int year, int month, int day, int hour, int minute) - { - GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); - cal.set(year, month, day, hour, minute); - return cal.getTimeInMillis(); - } - - public Timestamp timestamp(int year, int month, int day) { - return new Timestamp(unixTime(year, month, day)); - } - - @Test - public void nothing() - { - - } - - public static Database buildMemoryDatabase() - { - try - { - Database db = new JdbcDatabase( - DriverManager.getConnection("jdbc:sqlite::memory:")); - db.execute("pragma user_version=8;"); - MigrationHelper helper = new MigrationHelper(db); - helper.migrateTo(DATABASE_VERSION); - return db; - } - catch (SQLException e) - { - throw new RuntimeException(e); - } - } - - protected void copyAssetToFile(String assetPath, File dst) - throws IOException - { - IOUtils.copy(openAsset(assetPath), new FileOutputStream(dst)); - } - - @NonNull - protected InputStream openAsset(String assetPath) throws IOException - { - InputStream in = getClass().getResourceAsStream(assetPath); - if (in != null) return in; - - String pwd = Paths.get(".").toAbsolutePath().normalize().toString(); - String fullPath = pwd + "/assets/test/" + assetPath; - File file = new File(fullPath); - if (file.exists() && file.canRead()) in = new FileInputStream(file); - if (in != null) return in; - - throw new IllegalStateException("asset not found: " + fullPath); - } - - protected Database openDatabaseResource(String path) throws IOException - { - InputStream original = openAsset(path); - File tmpDbFile = File.createTempFile("database", ".db"); - tmpDbFile.deleteOnExit(); - IOUtils.copy(original, new FileOutputStream(tmpDbFile)); - return databaseOpener.open(tmpDbFile); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt new file mode 100644 index 000000000..f97991b6d --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/BaseUnitTest.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016-2021 Á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.core + +import com.nhaarman.mockitokotlin2.spy +import com.nhaarman.mockitokotlin2.validateMockitoUsage +import org.apache.commons.io.IOUtils +import org.isoron.uhabits.core.commands.CommandRunner +import org.isoron.uhabits.core.database.Database +import org.isoron.uhabits.core.database.DatabaseOpener +import org.isoron.uhabits.core.database.JdbcDatabase +import org.isoron.uhabits.core.database.MigrationHelper +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.ModelFactory +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.models.memory.MemoryModelFactory +import org.isoron.uhabits.core.tasks.SingleThreadTaskRunner +import org.isoron.uhabits.core.test.HabitFixtures +import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedLocalTime +import org.isoron.uhabits.core.utils.DateUtils.Companion.setStartDayOffset +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnitRunner +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.nio.file.Paths +import java.sql.DriverManager +import java.sql.SQLException + +@RunWith(MockitoJUnitRunner::class) +open class BaseUnitTest { + protected open lateinit var habitList: HabitList + protected lateinit var fixtures: HabitFixtures + protected lateinit var modelFactory: ModelFactory + protected lateinit var taskRunner: SingleThreadTaskRunner + protected open lateinit var commandRunner: CommandRunner + protected var databaseOpener: DatabaseOpener = object : DatabaseOpener { + override fun open(file: File): Database { + return try { + JdbcDatabase( + DriverManager.getConnection( + String.format( + "jdbc:sqlite:%s", + file.absolutePath + ) + ) + ) + } catch (e: SQLException) { + throw RuntimeException(e) + } + } + } + + @Before + @Throws(Exception::class) + open fun setUp() { + setFixedLocalTime(FIXED_LOCAL_TIME) + setStartDayOffset(0, 0) + val memoryModelFactory = MemoryModelFactory() + habitList = spy(memoryModelFactory.buildHabitList()) + fixtures = HabitFixtures(memoryModelFactory, habitList) + modelFactory = memoryModelFactory + taskRunner = SingleThreadTaskRunner() + commandRunner = CommandRunner(taskRunner) + } + + @After + @Throws(Exception::class) + open fun tearDown() { + validateMockitoUsage() + setFixedLocalTime(null) + setStartDayOffset(0, 0) + } + + fun unixTime(year: Int, month: Int, day: Int): Long { + val cal = getStartOfTodayCalendar() + cal.set(year, month, day, 0, 0, 0) + return cal.timeInMillis + } + + open fun unixTime(year: Int, month: Int, day: Int, hour: Int, minute: Int): Long { + val cal = getStartOfTodayCalendar() + cal.set(year, month, day, hour, minute) + return cal.timeInMillis + } + + fun timestamp(year: Int, month: Int, day: Int): Timestamp { + return Timestamp(unixTime(year, month, day)) + } + + @Test + fun nothing() { + } + + @Throws(IOException::class) + protected fun copyAssetToFile(assetPath: String, dst: File?) { + IOUtils.copy(openAsset(assetPath), FileOutputStream(dst!!)) + } + + @Throws(IOException::class) + protected fun openAsset(assetPath: String): InputStream { + var inputStream = javaClass.getResourceAsStream(assetPath) + if (inputStream != null) return inputStream + val pwd = Paths.get(".").toAbsolutePath().normalize().toString() + val fullPath = "$pwd/assets/test/$assetPath" + val file = File(fullPath) + if (file.exists() && file.canRead()) inputStream = FileInputStream(file) + if (inputStream != null) return inputStream + throw IllegalStateException("asset not found: $fullPath") + } + + @Throws(IOException::class) + protected fun openDatabaseResource(path: String): Database { + val original = openAsset(path) + val tmpDbFile = File.createTempFile("database", ".db") + tmpDbFile.deleteOnExit() + IOUtils.copy(original, FileOutputStream(tmpDbFile)) + return databaseOpener.open(tmpDbFile) + } + + companion object { + // 8:00am, January 25th, 2015 (UTC) + const val FIXED_LOCAL_TIME = 1422172800000L + fun buildMemoryDatabase(): Database { + return try { + val db: Database = JdbcDatabase( + DriverManager.getConnection("jdbc:sqlite::memory:") + ) + db.execute("pragma user_version=8;") + val helper = MigrationHelper(db) + helper.migrateTo(DATABASE_VERSION) + db + } catch (e: SQLException) { + throw RuntimeException(e) + } + } + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ArchiveHabitsCommandTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ArchiveHabitsCommandTest.kt similarity index 51% rename from uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ArchiveHabitsCommandTest.java rename to uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ArchiveHabitsCommandTest.kt index 219bcdf46..4a774340b 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ArchiveHabitsCommandTest.java +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ArchiveHabitsCommandTest.kt @@ -16,43 +16,31 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ - -package org.isoron.uhabits.core.commands; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; - -import java.util.*; - -import static org.junit.Assert.*; -import static org.hamcrest.MatcherAssert.assertThat; - -public class ArchiveHabitsCommandTest extends BaseUnitTest -{ - private ArchiveHabitsCommand command; - - private Habit habit; - - @Override +package org.isoron.uhabits.core.commands + +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.junit.Before +import org.junit.Test + +class ArchiveHabitsCommandTest : BaseUnitTest() { + private lateinit var command: ArchiveHabitsCommand + private lateinit var habit: Habit @Before - public void setUp() throws Exception - { - super.setUp(); - - habit = fixtures.createShortHabit(); - habitList.add(habit); - - command = new ArchiveHabitsCommand(habitList, - Collections.singletonList(habit)); + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createShortHabit() + habitList.add(habit) + command = ArchiveHabitsCommand(habitList, listOf(habit)) } @Test - public void testExecute() - { - assertFalse(habit.isArchived()); - command.run(); - assertTrue(habit.isArchived()); + fun testExecute() { + assertFalse(habit.isArchived) + command.run() + assertTrue(habit.isArchived) } - } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ChangeHabitColorCommandTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ChangeHabitColorCommandTest.java deleted file mode 100644 index 2506f3f8c..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ChangeHabitColorCommandTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.commands; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; - -import java.util.*; - -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.*; - -public class ChangeHabitColorCommandTest extends BaseUnitTest -{ - private ChangeHabitColorCommand command; - - private LinkedList selected; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - - selected = new LinkedList<>(); - - for (int i = 0; i < 3; i++) - { - Habit habit = fixtures.createShortHabit(); - habit.setColor(new PaletteColor(i + 1)); - selected.add(habit); - habitList.add(habit); - } - - command = new ChangeHabitColorCommand(habitList, selected, new PaletteColor(0)); - } - - @Test - public void testExecute() - { - checkOriginalColors(); - command.run(); - checkNewColors(); - } - - private void checkNewColors() - { - for (Habit h : selected) - assertThat(h.getColor(), equalTo(new PaletteColor(0))); - } - - private void checkOriginalColors() - { - int k = 0; - for (Habit h : selected) - assertThat(h.getColor(), equalTo(new PaletteColor(++k))); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ChangeHabitColorCommandTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ChangeHabitColorCommandTest.kt new file mode 100644 index 000000000..086d0c3df --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/ChangeHabitColorCommandTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016-2021 Á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.core.commands + +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.PaletteColor +import org.junit.Before +import org.junit.Test +import java.util.LinkedList + +class ChangeHabitColorCommandTest : BaseUnitTest() { + private lateinit var command: ChangeHabitColorCommand + private lateinit var selected: LinkedList + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + selected = LinkedList() + for (i in 0..2) { + val habit = fixtures.createShortHabit() + habit.color = PaletteColor(i + 1) + selected.add(habit) + habitList.add(habit) + } + command = ChangeHabitColorCommand(habitList, selected, PaletteColor(0)) + } + + @Test + fun testExecute() { + checkOriginalColors() + command.run() + checkNewColors() + } + + private fun checkNewColors() { + for (habit in selected) { + assertThat(habit.color, equalTo(PaletteColor(0))) + } + } + + private fun checkOriginalColors() { + var k = 0 + for (habit in selected) + assertThat(habit.color, equalTo(PaletteColor(++k))) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateHabitCommandTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateHabitCommandTest.java deleted file mode 100644 index 0f77712ea..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateHabitCommandTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.commands; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; - -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.*; - -public class CreateHabitCommandTest extends BaseUnitTest -{ - private CreateHabitCommand command; - - private Habit model; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - - model = fixtures.createEmptyHabit(); - model.setName("New habit"); - model.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - - command = new CreateHabitCommand(modelFactory, habitList, model); - } - - @Test - public void testExecute() - { - assertTrue(habitList.isEmpty()); - command.run(); - assertThat(habitList.size(), equalTo(1)); - Habit habit = habitList.getByPosition(0); - assertThat(habit.getName(), equalTo(model.getName())); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateHabitCommandTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateHabitCommandTest.kt new file mode 100644 index 000000000..f5d7f3fc0 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateHabitCommandTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016-2021 Á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.core.commands + +import junit.framework.Assert.assertTrue +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.Reminder +import org.isoron.uhabits.core.models.WeekdayList +import org.junit.Before +import org.junit.Test + +class CreateHabitCommandTest : BaseUnitTest() { + private lateinit var command: CreateHabitCommand + private lateinit var model: Habit + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + model = fixtures.createEmptyHabit() + model.name = "New habit" + model.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + command = CreateHabitCommand(modelFactory, habitList, model) + } + + @Test + fun testExecute() { + assertTrue(habitList.isEmpty) + command.run() + assertThat(habitList.size(), equalTo(1)) + val habit = habitList.getByPosition(0) + assertThat(habit.name, equalTo(model.name)) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.java deleted file mode 100644 index 0ad5fdee4..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.commands; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import static org.isoron.uhabits.core.models.Entry.*; -import static org.junit.Assert.*; - -public class CreateRepetitionCommandTest extends BaseUnitTest -{ - private CreateRepetitionCommand command; - - private Habit habit; - - private Timestamp today; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - - habit = fixtures.createShortHabit(); - habitList.add(habit); - - today = DateUtils.getToday(); - command = new CreateRepetitionCommand(habitList, habit, today, 100); - } - - @Test - public void testExecute() - { - EntryList entries = habit.getOriginalEntries(); - Entry entry = entries.get(today); - assertEquals(YES_MANUAL, entry.getValue()); - - command.run(); - entry = entries.get(today); - assertEquals(100, entry.getValue()); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.kt new file mode 100644 index 000000000..fade73e69 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/CreateRepetitionCommandTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016-2021 Á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.core.commands + +import junit.framework.Assert.assertEquals +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.junit.Before +import org.junit.Test + +class CreateRepetitionCommandTest : BaseUnitTest() { + private lateinit var command: CreateRepetitionCommand + private lateinit var habit: Habit + private lateinit var today: Timestamp + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createShortHabit() + habitList.add(habit) + today = getToday() + command = CreateRepetitionCommand(habitList, habit, today, 100) + } + + @Test + fun testExecute() { + val entries = habit.originalEntries + var entry = entries.get(today) + assertEquals(Entry.YES_MANUAL, entry.value) + command.run() + entry = entries.get(today) + assertEquals(100, entry.value.toLong()) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/DeleteHabitsCommandTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/DeleteHabitsCommandTest.java deleted file mode 100644 index 97e5897b1..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/DeleteHabitsCommandTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.commands; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; -import org.junit.rules.*; - -import java.util.*; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; - -public class DeleteHabitsCommandTest extends BaseUnitTest -{ - private DeleteHabitsCommand command; - - private LinkedList selected; - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - selected = new LinkedList<>(); - - // Habits that should be deleted - for (int i = 0; i < 3; i++) - { - Habit habit = fixtures.createShortHabit(); - habitList.add(habit); - selected.add(habit); - } - - // Extra habit that should not be deleted - Habit extraHabit = fixtures.createShortHabit(); - extraHabit.setName("extra"); - habitList.add(extraHabit); - - command = new DeleteHabitsCommand(habitList, selected); - } - - @Test - public void testExecute() - { - assertThat(habitList.size(), equalTo(4)); - - command.run(); - assertThat(habitList.size(), equalTo(1)); - assertThat(habitList.getByPosition(0).getName(), equalTo("extra")); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/DeleteHabitsCommandTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/DeleteHabitsCommandTest.kt new file mode 100644 index 000000000..b44328035 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/DeleteHabitsCommandTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016-2021 Á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.core.commands + +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import java.util.LinkedList + +class DeleteHabitsCommandTest : BaseUnitTest() { + private lateinit var command: DeleteHabitsCommand + private lateinit var selected: LinkedList + + @get:Rule + var thrown = ExpectedException.none()!! + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + selected = LinkedList() + + // Habits that should be deleted + for (i in 0..2) { + val habit = fixtures.createShortHabit() + habitList.add(habit) + selected.add(habit) + } + + // Extra habit that should not be deleted + val extraHabit = fixtures.createShortHabit() + extraHabit.name = "extra" + habitList.add(extraHabit) + command = DeleteHabitsCommand(habitList, selected) + } + + @Test + fun testExecute() { + assertThat(habitList.size(), equalTo(4)) + command.run() + assertThat(habitList.size(), equalTo(1)) + assertThat(habitList.getByPosition(0).name, equalTo("extra")) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.java deleted file mode 100644 index bef05430d..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.commands; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; - -public class EditHabitCommandTest extends BaseUnitTest -{ - private EditHabitCommand command; - - private Habit habit; - - private Habit modified; - - private Timestamp today; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - - habit = fixtures.createShortHabit(); - habit.setName("original"); - habit.setFrequency(Frequency.DAILY); - habit.recompute(); - habitList.add(habit); - - modified = fixtures.createEmptyHabit(); - modified.copyFrom(habit); - modified.setName("modified"); - habitList.add(modified); - - today = DateUtils.getTodayWithOffset(); - } - - @Test - public void testExecute() - { - command = new EditHabitCommand(habitList, habit.getId(), modified); - - double originalScore = habit.getScores().get(today).getValue(); - assertThat(habit.getName(), equalTo("original")); - - command.run(); - assertThat(habit.getName(), equalTo("modified")); - assertThat(habit.getScores().get(today).getValue(), equalTo(originalScore)); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.kt new file mode 100644 index 000000000..2ce4b8e86 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/EditHabitCommandTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016-2021 Á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.core.commands + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Frequency +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset +import org.junit.Before +import org.junit.Test + +class EditHabitCommandTest : BaseUnitTest() { + private lateinit var command: EditHabitCommand + private lateinit var habit: Habit + private lateinit var modified: Habit + private lateinit var today: Timestamp + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createShortHabit() + habit.name = "original" + habit.frequency = Frequency.DAILY + habit.recompute() + habitList.add(habit) + modified = fixtures.createEmptyHabit() + modified.copyFrom(habit) + modified.name = "modified" + habitList.add(modified) + today = getTodayWithOffset() + } + + @Test + fun testExecute() { + command = EditHabitCommand(habitList, habit.id!!, modified) + val originalScore = habit.scores[today].value + assertThat(habit.name, equalTo("original")) + command.run() + assertThat(habit.name, equalTo("modified")) + assertThat(habit.scores[today].value, equalTo(originalScore)) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/UnarchiveHabitsCommandTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/UnarchiveHabitsCommandTest.kt similarity index 51% rename from uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/UnarchiveHabitsCommandTest.java rename to uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/UnarchiveHabitsCommandTest.kt index 7756272a0..cf334e767 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/UnarchiveHabitsCommandTest.java +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/commands/UnarchiveHabitsCommandTest.kt @@ -16,43 +16,33 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.core.commands -package org.isoron.uhabits.core.commands; +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.junit.Before +import org.junit.Test -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; +class UnarchiveHabitsCommandTest : BaseUnitTest() { + private lateinit var command: UnarchiveHabitsCommand + private lateinit var habit: Habit -import java.util.*; - -import static org.junit.Assert.*; - -public class UnarchiveHabitsCommandTest extends BaseUnitTest -{ - private UnarchiveHabitsCommand command; - private Habit habit; - - @Override @Before - public void setUp() throws Exception - { - super.setUp(); - - habit = fixtures.createShortHabit(); - habit.setArchived(true); - habitList.add(habit); - - command = new UnarchiveHabitsCommand(habitList, Collections - .singletonList - (habit)); + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createShortHabit() + habit.isArchived = true + habitList.add(habit) + command = UnarchiveHabitsCommand(habitList, listOf(habit)) } @Test - public void testExecuteUndoRedo() - { - assertTrue(habit.isArchived()); - command.run(); - assertFalse(habit.isArchived()); + fun testExecuteUndoRedo() { + assertTrue(habit.isArchived) + command.run() + assertFalse(habit.isArchived) } - } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/RepositoryTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/RepositoryTest.java index 31d09d398..232ad25ff 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/RepositoryTest.java +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/RepositoryTest.java @@ -40,7 +40,7 @@ public class RepositoryTest extends BaseUnitTest public void setUp() throws Exception { super.setUp(); - this.db = buildMemoryDatabase(); + this.db = BaseUnitTest.Companion.buildMemoryDatabase(); repository = new Repository<>(ThingRecord.class, db); db.execute("drop table if exists tests"); diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version22Test.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version22Test.java deleted file mode 100644 index 04567d131..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version22Test.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.database.migrations; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.database.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.models.sqlite.*; -import org.isoron.uhabits.core.test.*; -import org.junit.*; -import org.junit.rules.*; - -import static junit.framework.TestCase.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; - -public class Version22Test extends BaseUnitTest -{ - @Rule - public ExpectedException exception = ExpectedException.none(); - - private Database db; - - private MigrationHelper helper; - - @Override - public void setUp() throws Exception - { - super.setUp(); - db = openDatabaseResource("/databases/021.db"); - helper = new MigrationHelper(db); - modelFactory = new SQLModelFactory(db); - habitList = modelFactory.buildHabitList(); - fixtures = new HabitFixtures(modelFactory, habitList); - } - - @Test - public void testKeepValidReps() throws Exception - { - db.query("select count(*) from repetitions", - (c) -> assertThat(c.getInt(0), equalTo(3))); - - helper.migrateTo(22); - - db.query("select count(*) from repetitions", - (c) -> assertThat(c.getInt(0), equalTo(3))); - } - - @Test - public void testRemoveRepsWithInvalidId() throws Exception - { - db.execute("insert into Repetitions(habit, timestamp, value) " + - "values (99999, 100, 2)"); - db.query("select count(*) from repetitions where habit = 99999", - (c) -> assertThat(c.getInt(0), equalTo(1))); - - helper.migrateTo(22); - - db.query("select count(*) from repetitions where habit = 99999", - (c) -> assertThat(c.getInt(0), equalTo(0))); - } - - @Test - public void testDisallowNewRepsWithInvalidRef() throws Exception - { - helper.migrateTo(22); - exception.expectMessage(containsString("SQLITE_CONSTRAINT")); - db.execute("insert into Repetitions(habit, timestamp, value) " + - "values (99999, 100, 2)"); - } - - @Test - public void testRemoveRepetitionsWithNullTimestamp() throws Exception - { - db.execute("insert into repetitions(habit, value) values (0, 2)"); - db.query("select count(*) from repetitions where timestamp is null", - (c) -> assertThat(c.getInt(0), equalTo(1))); - - helper.migrateTo(22); - - db.query("select count(*) from repetitions where timestamp is null", - (c) -> assertThat(c.getInt(0), equalTo(0))); - } - - @Test - public void testDisallowNullTimestamp() throws Exception - { - helper.migrateTo(22); - exception.expectMessage(containsString("SQLITE_CONSTRAINT")); - db.execute("insert into Repetitions(habit, value) " + "values (0, 2)"); - } - - @Test - public void testRemoveRepetitionsWithNullHabit() throws Exception - { - db.execute("insert into repetitions(timestamp, value) values (0, 2)"); - db.query("select count(*) from repetitions where habit is null", - (c) -> assertThat(c.getInt(0), equalTo(1))); - - helper.migrateTo(22); - - db.query("select count(*) from repetitions where habit is null", - (c) -> assertThat(c.getInt(0), equalTo(0))); - } - - @Test - public void testDisallowNullHabit() throws Exception - { - helper.migrateTo(22); - exception.expectMessage(containsString("SQLITE_CONSTRAINT")); - db.execute( - "insert into Repetitions(timestamp, value) " + "values (5, 2)"); - } - - @Test - public void testRemoveDuplicateRepetitions() throws Exception - { - db.execute("insert into repetitions(habit, timestamp, value)" + - "values (0, 100, 2)"); - db.execute("insert into repetitions(habit, timestamp, value)" + - "values (0, 100, 5)"); - db.execute("insert into repetitions(habit, timestamp, value)" + - "values (0, 100, 10)"); - db.query( - "select count(*) from repetitions where timestamp=100 and habit=0", - (c) -> assertThat(c.getInt(0), equalTo(3))); - - helper.migrateTo(22); - - db.query( - "select count(*) from repetitions where timestamp=100 and habit=0", - (c) -> assertThat(c.getInt(0), equalTo(1))); - } - - @Test - public void testDisallowNewDuplicateTimestamps() throws Exception - { - helper.migrateTo(22); - db.execute("insert into repetitions(habit, timestamp, value)" + - "values (0, 100, 2)"); - exception.expectMessage(containsString("SQLITE_CONSTRAINT")); - db.execute("insert into repetitions(habit, timestamp, value)" + - "values (0, 100, 5)"); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version22Test.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version22Test.kt new file mode 100644 index 000000000..9a9927cc1 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version22Test.kt @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016-2021 Á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.core.database.migrations + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers +import org.hamcrest.Matchers.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.database.Cursor +import org.isoron.uhabits.core.database.Database +import org.isoron.uhabits.core.database.MigrationHelper +import org.isoron.uhabits.core.models.sqlite.SQLModelFactory +import org.isoron.uhabits.core.test.HabitFixtures +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException + +class Version22Test : BaseUnitTest() { + @get:Rule + var exception = ExpectedException.none()!! + private lateinit var db: Database + private lateinit var helper: MigrationHelper + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + db = openDatabaseResource("/databases/021.db") + helper = MigrationHelper(db) + modelFactory = SQLModelFactory(db) + habitList = (modelFactory as SQLModelFactory).buildHabitList() + fixtures = HabitFixtures(modelFactory, habitList) + } + + @Test + @Throws(Exception::class) + fun testKeepValidReps() { + db.query("select count(*) from repetitions") { c: Cursor -> + assertThat(c.getInt(0), equalTo(3)) + } + helper.migrateTo(22) + db.query("select count(*) from repetitions") { c: Cursor -> + assertThat(c.getInt(0), equalTo(3)) + } + } + + @Test + @Throws(Exception::class) + fun testRemoveRepsWithInvalidId() { + db.execute("insert into Repetitions(habit, timestamp, value) values (99999, 100, 2)") + db.query("select count(*) from repetitions where habit = 99999") { c: Cursor -> + assertThat(c.getInt(0), equalTo(1)) + } + helper.migrateTo(22) + db.query("select count(*) from repetitions where habit = 99999") { c: Cursor -> + assertThat(c.getInt(0), equalTo(0)) + } + } + + @Test + @Throws(Exception::class) + fun testDisallowNewRepsWithInvalidRef() { + helper.migrateTo(22) + exception.expectMessage(Matchers.containsString("SQLITE_CONSTRAINT")) + db.execute("insert into Repetitions(habit, timestamp, value) values (99999, 100, 2)") + } + + @Test + @Throws(Exception::class) + fun testRemoveRepetitionsWithNullTimestamp() { + db.execute("insert into repetitions(habit, value) values (0, 2)") + db.query("select count(*) from repetitions where timestamp is null") { c: Cursor -> + assertThat(c.getInt(0), equalTo(1)) + } + helper.migrateTo(22) + db.query("select count(*) from repetitions where timestamp is null") { c: Cursor -> + assertThat(c.getInt(0), equalTo(0)) + } + } + + @Test + @Throws(Exception::class) + fun testDisallowNullTimestamp() { + helper.migrateTo(22) + exception.expectMessage(Matchers.containsString("SQLITE_CONSTRAINT")) + db.execute("insert into Repetitions(habit, value) " + "values (0, 2)") + } + + @Test + @Throws(Exception::class) + fun testRemoveRepetitionsWithNullHabit() { + db.execute("insert into repetitions(timestamp, value) values (0, 2)") + db.query("select count(*) from repetitions where habit is null") { c: Cursor -> + assertThat(c.getInt(0), equalTo(1)) + } + helper.migrateTo(22) + db.query("select count(*) from repetitions where habit is null") { c: Cursor -> + assertThat(c.getInt(0), equalTo(0)) + } + } + + @Test + @Throws(Exception::class) + fun testDisallowNullHabit() { + helper.migrateTo(22) + exception.expectMessage(Matchers.containsString("SQLITE_CONSTRAINT")) + db.execute("insert into Repetitions(timestamp, value) " + "values (5, 2)") + } + + @Test + @Throws(Exception::class) + fun testRemoveDuplicateRepetitions() { + db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 2)") + db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 5)") + db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 10)") + db.query("select count(*) from repetitions where timestamp=100 and habit=0") { c: Cursor -> + assertThat(c.getInt(0), equalTo(3)) + } + helper.migrateTo(22) + db.query("select count(*) from repetitions where timestamp=100 and habit=0") { c: Cursor -> + assertThat(c.getInt(0), equalTo(1)) + } + } + + @Test + @Throws(Exception::class) + fun testDisallowNewDuplicateTimestamps() { + helper.migrateTo(22) + db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 2)") + exception.expectMessage(Matchers.containsString("SQLITE_CONSTRAINT")) + db.execute("insert into repetitions(habit, timestamp, value)values (0, 100, 5)") + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version23Test.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version23Test.kt index c9bcbe90f..b68e96342 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version23Test.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/database/migrations/Version23Test.kt @@ -20,7 +20,7 @@ package org.isoron.uhabits.core.database.migrations import org.hamcrest.CoreMatchers.equalTo -import org.hamcrest.MatcherAssert +import org.hamcrest.MatcherAssert.assertThat import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.database.Database import org.isoron.uhabits.core.database.MigrationHelper @@ -39,7 +39,7 @@ class Version23Test : BaseUnitTest() { db = openDatabaseResource("/databases/022.db") helper = MigrationHelper(db) modelFactory = SQLModelFactory(db) - habitList = modelFactory.buildHabitList() + habitList = (modelFactory as SQLModelFactory).buildHabitList() fixtures = HabitFixtures(modelFactory, habitList) } @@ -66,7 +66,7 @@ class Version23Test : BaseUnitTest() { for (i in 0 until descriptions.size) { cursor.moveToNext() - MatcherAssert.assertThat(cursor.getString(0), equalTo(descriptions[i])) + assertThat(cursor.getString(0), equalTo(descriptions[i])) } } @@ -76,7 +76,7 @@ class Version23Test : BaseUnitTest() { val cursor = db.query("select description from Habits") while (cursor.moveToNext()) { - MatcherAssert.assertThat(cursor.getString(0), equalTo("")) + assertThat(cursor.getString(0), equalTo("")) } } } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.java deleted file mode 100644 index 8d32889bc..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.io; - -import org.apache.commons.io.*; -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; - -import java.io.*; -import java.nio.file.*; -import java.util.*; -import java.util.zip.*; - -import static org.junit.Assert.*; - -public class HabitsCSVExporterTest extends BaseUnitTest -{ - private File baseDir; - - @Before - public void setUp() throws Exception - { - super.setUp(); - habitList.add(fixtures.createShortHabit()); - habitList.add(fixtures.createEmptyHabit()); - baseDir = Files.createTempDirectory("csv").toFile(); - assertNotNull(baseDir); - } - - @Override - public void tearDown() throws Exception - { - FileUtils.deleteDirectory(baseDir); - super.tearDown(); - } - - @Test - public void testExportCSV() throws IOException - { - List selected = new LinkedList<>(); - for (Habit h : habitList) selected.add(h); - - HabitsCSVExporter exporter = - new HabitsCSVExporter(habitList, selected, baseDir); - String filename = exporter.writeArchive(); - assertAbsolutePathExists(filename); - - File archive = new File(filename); - unzip(archive); - - assertPathExists("Habits.csv"); - assertPathExists("001 Meditate/Checkmarks.csv"); - assertPathExists("001 Meditate/Scores.csv"); - assertPathExists("002 Wake up early"); - assertPathExists("002 Wake up early/Checkmarks.csv"); - assertPathExists("002 Wake up early/Scores.csv"); - assertPathExists("Checkmarks.csv"); - assertPathExists("Scores.csv"); - } - - 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 out = new File(outputFilename); - File parent = out.getParentFile(); - if (parent != null) parent.mkdirs(); - - IOUtils.copy(stream, new FileOutputStream(out)); - } - - zip.close(); - } - -// @Test -// public void test_writeCSV() throws IOException -// { -// Habit habit = fixtures.createShortHabit(); -// -// String expectedCSV = -// "2015-01-25,0.2557\n" + -// "2015-01-24,0.2226\n" + -// "2015-01-23,0.1991\n" + -// "2015-01-22,0.1746\n" + -// "2015-01-21,0.1379\n" + -// "2015-01-20,0.0995\n" + -// "2015-01-19,0.0706\n" + -// "2015-01-18,0.0515\n" + -// "2015-01-17,0.0315\n" + -// "2015-01-16,0.0107\n"; -// -// StringWriter writer = new StringWriter(); -// habit.getScores().writeCSV(writer); -// -// assertThat(writer.toString(), equalTo(expectedCSV)); -// } - - private void assertPathExists(String s) - { - assertAbsolutePathExists( - String.format("%s/%s", baseDir.getAbsolutePath(), s)); - } - - private void assertAbsolutePathExists(String s) - { - File file = new File(s); - assertTrue( - String.format("File %s should exist", file.getAbsolutePath()), - file.exists()); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.kt new file mode 100644 index 000000000..c6655bba6 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/HabitsCSVExporterTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016-2021 Á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.core.io + +import junit.framework.Assert.assertTrue +import org.apache.commons.io.FileUtils +import org.apache.commons.io.IOUtils +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.junit.Before +import org.junit.Test +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.nio.file.Files +import java.util.LinkedList +import java.util.zip.ZipFile + +class HabitsCSVExporterTest : BaseUnitTest() { + private lateinit var baseDir: File + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habitList.add(fixtures.createShortHabit()) + habitList.add(fixtures.createEmptyHabit()) + baseDir = Files.createTempDirectory("csv").toFile() + } + + @Throws(Exception::class) + override fun tearDown() { + FileUtils.deleteDirectory(baseDir) + super.tearDown() + } + + @Test + @Throws(IOException::class) + fun testExportCSV() { + val selected: MutableList = LinkedList() + for (h in habitList) selected.add(h) + val exporter = HabitsCSVExporter( + habitList, + selected, + baseDir + ) + val filename = exporter.writeArchive() + assertAbsolutePathExists(filename) + val archive = File(filename) + unzip(archive) + assertPathExists("Habits.csv") + assertPathExists("001 Meditate/Checkmarks.csv") + assertPathExists("001 Meditate/Scores.csv") + assertPathExists("002 Wake up early") + assertPathExists("002 Wake up early/Checkmarks.csv") + assertPathExists("002 Wake up early/Scores.csv") + assertPathExists("Checkmarks.csv") + assertPathExists("Scores.csv") + } + + @Throws(IOException::class) + private fun unzip(file: File) { + val zip = ZipFile(file) + val e = zip.entries() + while (e.hasMoreElements()) { + val entry = e.nextElement() + val stream = zip.getInputStream(entry) + val outputFilename = String.format( + "%s/%s", + baseDir.absolutePath, + entry.name + ) + val out = File(outputFilename) + val parent = out.parentFile + parent?.mkdirs() + IOUtils.copy(stream, FileOutputStream(out)) + } + zip.close() + } + + private fun assertPathExists(s: String) { + assertAbsolutePathExists(String.format("%s/%s", baseDir.absolutePath, s)) + } + + private fun assertAbsolutePathExists(s: String) { + val file = File(s) + assertTrue( + String.format("File %s should exist", file.absolutePath), + file.exists() + ) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.java deleted file mode 100644 index 8bb89cd49..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.io; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import java.io.*; -import java.util.*; - -import static junit.framework.TestCase.assertFalse; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsEqual.*; -import static org.isoron.uhabits.core.models.Entry.*; -import static org.isoron.uhabits.core.models.Frequency.*; -import static org.junit.Assert.assertTrue; - -public class ImportTest extends BaseUnitTest -{ - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - DateUtils.setFixedLocalTime(null); - } - - @Test - public void testHabitBullCSV() throws IOException - { - importFromFile("habitbull.csv"); - - assertThat(habitList.size(), equalTo(4)); - - Habit habit = habitList.getByPosition(0); - assertThat(habit.getName(), equalTo("Breed dragons")); - assertThat(habit.getDescription(), equalTo("with love and fire")); - assertThat(habit.getFrequency(), equalTo(Frequency.DAILY)); - assertTrue(isChecked(habit, 2016, 3, 18)); - assertTrue(isChecked(habit, 2016, 3, 19)); - assertFalse(isChecked(habit, 2016, 3, 20)); - } - - @Test - public void testLoopDB() throws IOException - { - importFromFile("loop.db"); - - assertThat(habitList.size(), equalTo(9)); - - Habit habit = habitList.getByPosition(0); - assertThat(habit.getName(), equalTo("Wake up early")); - assertThat(habit.getFrequency(), equalTo(THREE_TIMES_PER_WEEK)); - assertTrue(isChecked(habit, 2016, 3, 14)); - assertTrue(isChecked(habit, 2016, 3, 16)); - assertFalse(isChecked(habit, 2016, 3, 17)); - } - - @Test - public void testRewireDB() throws IOException - { - importFromFile("rewire.db"); - - assertThat(habitList.size(), equalTo(3)); - - Habit habit = habitList.getByPosition(1); - assertThat(habit.getName(), equalTo("Wake up early")); - assertThat(habit.getFrequency(), equalTo(THREE_TIMES_PER_WEEK)); - assertFalse(habit.hasReminder()); - assertFalse(isChecked(habit, 2015, 12, 31)); - assertTrue(isChecked(habit, 2016, 1, 18)); - assertTrue(isChecked(habit, 2016, 1, 28)); - assertFalse(isChecked(habit, 2016, 3, 10)); - - habit = habitList.getByPosition(2); - assertThat(habit.getName(), equalTo("brush teeth")); - assertThat(habit.getFrequency(), equalTo(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().toArray(), equalTo(reminderDays)); - } - - @Test - public void testTickmateDB() throws IOException - { - importFromFile("tickmate.db"); - - assertThat(habitList.size(), equalTo(3)); - - Habit h = habitList.getByPosition(2); - assertThat(h.getName(), equalTo("Vegan")); - assertTrue(isChecked(h, 2016, 1, 24)); - assertTrue(isChecked(h, 2016, 2, 5)); - assertTrue(isChecked(h, 2016, 3, 18)); - assertFalse(isChecked(h, 2016, 3, 14)); - } - - private boolean isChecked(Habit h, int year, int month, int day) - { - GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); - date.set(year, month - 1, day); - Timestamp timestamp = new Timestamp(date); - return h.getOriginalEntries().get(timestamp).getValue() == YES_MANUAL; - } - - private void importFromFile(String assetFilename) throws IOException - { - File file = File.createTempFile("asset", ""); - copyAssetToFile(assetFilename, file); - assertTrue(file.exists()); - assertTrue(file.canRead()); - - GenericImporter importer = new GenericImporter( - new LoopDBImporter(habitList, modelFactory, databaseOpener, commandRunner, new StandardLogging()), - new RewireDBImporter(habitList, modelFactory, databaseOpener), - new TickmateDBImporter(habitList, modelFactory, databaseOpener), - new HabitBullCSVImporter(habitList, modelFactory)); - - assertTrue(importer.canHandle(file)); - importer.importHabitsFromFile(file); - - file.delete(); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.kt new file mode 100644 index 000000000..0be326074 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016-2021 Á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.core.io + +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.core.IsEqual.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.models.Frequency +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedLocalTime +import org.junit.Before +import org.junit.Test +import java.io.File +import java.io.IOException + +class ImportTest : BaseUnitTest() { + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + setFixedLocalTime(null) + } + + @Test + @Throws(IOException::class) + fun testHabitBullCSV() { + importFromFile("habitbull.csv") + assertThat(habitList.size(), equalTo(4)) + val habit = habitList.getByPosition(0) + assertThat(habit.name, equalTo("Breed dragons")) + assertThat(habit.description, equalTo("with love and fire")) + assertThat(habit.frequency, equalTo(Frequency.DAILY)) + assertTrue(isChecked(habit, 2016, 3, 18)) + assertTrue(isChecked(habit, 2016, 3, 19)) + assertFalse(isChecked(habit, 2016, 3, 20)) + } + + @Test + @Throws(IOException::class) + fun testLoopDB() { + importFromFile("loop.db") + assertThat(habitList.size(), equalTo(9)) + val habit = habitList.getByPosition(0) + assertThat(habit.name, equalTo("Wake up early")) + assertThat(habit.frequency, equalTo(Frequency.THREE_TIMES_PER_WEEK)) + assertTrue(isChecked(habit, 2016, 3, 14)) + assertTrue(isChecked(habit, 2016, 3, 16)) + assertFalse(isChecked(habit, 2016, 3, 17)) + } + + @Test + @Throws(IOException::class) + fun testRewireDB() { + importFromFile("rewire.db") + assertThat(habitList.size(), equalTo(3)) + var habit = habitList.getByPosition(1) + assertThat(habit.name, equalTo("Wake up early")) + assertThat(habit.frequency, equalTo(Frequency.THREE_TIMES_PER_WEEK)) + assertFalse(habit.hasReminder()) + assertFalse(isChecked(habit, 2015, 12, 31)) + assertTrue(isChecked(habit, 2016, 1, 18)) + assertTrue(isChecked(habit, 2016, 1, 28)) + assertFalse(isChecked(habit, 2016, 3, 10)) + habit = habitList.getByPosition(2) + assertThat(habit.name, equalTo("brush teeth")) + assertThat(habit.frequency, equalTo(Frequency.THREE_TIMES_PER_WEEK)) + assertThat(habit.hasReminder(), equalTo(true)) + val reminder = habit.reminder + assertThat(reminder!!.hour, equalTo(8)) + assertThat(reminder.minute, equalTo(0)) + val reminderDays = booleanArrayOf(false, true, true, true, true, true, false) + assertThat(reminder.days.toArray(), equalTo(reminderDays)) + } + + @Test + @Throws(IOException::class) + fun testTickmateDB() { + importFromFile("tickmate.db") + assertThat(habitList.size(), equalTo(3)) + val h = habitList.getByPosition(2) + assertThat(h.name, equalTo("Vegan")) + assertTrue(isChecked(h, 2016, 1, 24)) + assertTrue(isChecked(h, 2016, 2, 5)) + assertTrue(isChecked(h, 2016, 3, 18)) + assertFalse(isChecked(h, 2016, 3, 14)) + } + + private fun isChecked(h: Habit, year: Int, month: Int, day: Int): Boolean { + val date = getStartOfTodayCalendar() + date.set(year, month - 1, day) + val timestamp = Timestamp(date) + return h.originalEntries.get(timestamp).value == Entry.YES_MANUAL + } + + @Throws(IOException::class) + private fun importFromFile(assetFilename: String) { + val file = File.createTempFile("asset", "") + copyAssetToFile(assetFilename, file) + assertTrue(file.exists()) + assertTrue(file.canRead()) + val importer = GenericImporter( + LoopDBImporter( + habitList, + modelFactory, + databaseOpener, + commandRunner, + StandardLogging() + ), + RewireDBImporter(habitList, modelFactory, databaseOpener), + TickmateDBImporter(habitList, modelFactory, databaseOpener), + HabitBullCSVImporter(habitList, modelFactory) + ) + assertTrue(importer.canHandle(file)) + importer.importHabitsFromFile(file) + file.delete() + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/EntryListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/EntryListTest.kt index 9c7de4bb3..744b21a62 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/EntryListTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/EntryListTest.kt @@ -381,5 +381,5 @@ class EntryListTest { } } - fun day(offset: Int) = DateUtils.getToday().minus(offset) + fun day(offset: Int) = DateUtils.getToday().minus(offset)!! } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitListTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitListTest.java deleted file mode 100644 index 50a743849..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitListTest.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models; - -import org.isoron.uhabits.core.*; -import org.junit.*; -import org.junit.rules.*; - -import java.io.*; -import java.util.*; - -import static java.lang.Math.*; -import static junit.framework.TestCase.assertFalse; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.isoron.uhabits.core.models.HabitList.Order.*; -import static org.junit.Assert.*; - -@SuppressWarnings("JavaDoc") -public class HabitListTest extends BaseUnitTest -{ - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private ArrayList habitsArray; - - private HabitList activeHabits; - - private HabitList reminderHabits; - - @Override - public void setUp() throws Exception - { - super.setUp(); - habitsArray = new ArrayList<>(); - - for (int i = 0; i < 10; i++) - { - Habit habit = fixtures.createEmptyHabit(); - habitList.add(habit); - habitsArray.add(habit); - - if (i % 3 == 0) - habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - } - - habitsArray.get(0).setArchived(true); - habitsArray.get(1).setArchived(true); - habitsArray.get(4).setArchived(true); - habitsArray.get(7).setArchived(true); - - activeHabits = habitList.getFiltered(new HabitMatcherBuilder().build()); - - reminderHabits = habitList.getFiltered(new HabitMatcherBuilder() - .setArchivedAllowed(true) - .setReminderRequired(true) - .build()); - } - - @Test - public void testSize() - { - assertThat(habitList.size(), equalTo(10)); - assertThat(activeHabits.size(), equalTo(6)); - assertThat(reminderHabits.size(), equalTo(4)); - } - - @Test - public void testGetByPosition() - { - assertThat(habitList.getByPosition(0), equalTo(habitsArray.get(0))); - assertThat(habitList.getByPosition(3), equalTo(habitsArray.get(3))); - assertThat(habitList.getByPosition(9), equalTo(habitsArray.get(9))); - - assertThat(activeHabits.getByPosition(0), equalTo(habitsArray.get(2))); - assertThat(reminderHabits.getByPosition(1), - equalTo(habitsArray.get(3))); - } - - @Test - public void testGetById() - { - Habit habit1 = habitsArray.get(0); - Habit habit2 = habitList.getById(habit1.getId()); - assertThat(habit1, equalTo(habit2)); - } - - @Test - public void testGetById_withInvalidId() - { - assertNull(habitList.getById(100L)); - } - - @Test - public void testOrdering() - { - Habit h1 = fixtures.createEmptyHabit(); - h1.setName("A Habit"); - h1.setColor(new PaletteColor(2)); - h1.setPosition(1); - - Habit h2 = fixtures.createEmptyHabit(); - h2.setName("B Habit"); - h2.setColor(new PaletteColor(2)); - h2.setPosition(3); - - Habit h3 = fixtures.createEmptyHabit(); - h3.setName("C Habit"); - h3.setColor(new PaletteColor(0)); - h3.setPosition(0); - - Habit h4 = fixtures.createEmptyHabit(); - h4.setName("D Habit"); - h4.setColor(new PaletteColor(1)); - h4.setPosition(2); - - HabitList list = modelFactory.buildHabitList(); - - list.add(h3); - list.add(h1); - list.add(h4); - list.add(h2); - - list.setPrimaryOrder(BY_POSITION); - assertThat(list.getByPosition(0), equalTo(h3)); - assertThat(list.getByPosition(1), equalTo(h1)); - assertThat(list.getByPosition(2), equalTo(h4)); - assertThat(list.getByPosition(3), equalTo(h2)); - - list.setPrimaryOrder(BY_NAME_DESC); - assertThat(list.getByPosition(0), equalTo(h4)); - assertThat(list.getByPosition(1), equalTo(h3)); - assertThat(list.getByPosition(2), equalTo(h2)); - assertThat(list.getByPosition(3), equalTo(h1)); - - list.setPrimaryOrder(BY_NAME_ASC); - assertThat(list.getByPosition(0), equalTo(h1)); - assertThat(list.getByPosition(1), equalTo(h2)); - assertThat(list.getByPosition(2), equalTo(h3)); - assertThat(list.getByPosition(3), equalTo(h4)); - - list.setPrimaryOrder(BY_NAME_ASC); - list.remove(h1); - list.add(h1); - assertThat(list.getByPosition(0), equalTo(h1)); - - list.setPrimaryOrder(BY_COLOR_ASC); - list.setSecondaryOrder(BY_NAME_ASC); - assertThat(list.getByPosition(0), equalTo(h3)); - assertThat(list.getByPosition(1), equalTo(h4)); - assertThat(list.getByPosition(2), equalTo(h1)); - assertThat(list.getByPosition(3), equalTo(h2)); - - list.setPrimaryOrder(BY_COLOR_DESC); - list.setSecondaryOrder(BY_NAME_ASC); - assertThat(list.getByPosition(0), equalTo(h1)); - assertThat(list.getByPosition(1), equalTo(h2)); - assertThat(list.getByPosition(2), equalTo(h4)); - assertThat(list.getByPosition(3), equalTo(h3)); - - list.setPrimaryOrder(BY_POSITION); - assertThat(list.getByPosition(0), equalTo(h3)); - assertThat(list.getByPosition(1), equalTo(h1)); - assertThat(list.getByPosition(2), equalTo(h4)); - assertThat(list.getByPosition(3), equalTo(h2)); - } - - @Test - public void testReorder() { - int operations[][] = { - { 5, 2 }, { 3, 7 }, { 4, 4 }, { 8, 3 } - }; - - int expectedSequence[][] = { - { 0, 1, 5, 2, 3, 4, 6, 7, 8, 9 }, - { 0, 1, 5, 2, 4, 6, 7, 3, 8, 9 }, - { 0, 1, 5, 2, 4, 6, 7, 3, 8, 9 }, - { 0, 1, 5, 2, 4, 6, 7, 8, 3, 9 }, - }; - - for (int i = 0; i < operations.length; i++) - { - Habit fromHabit = habitsArray.get(operations[i][0]); - Habit toHabit = habitsArray.get(operations[i][1]); - habitList.reorder(fromHabit, toHabit); - - int actualSequence[] = new int[10]; - for (int j = 0; j < 10; j++) - { - Habit h = habitList.getByPosition(j); - assertThat(h.getPosition(), equalTo(j)); - actualSequence[j] = toIntExact(h.getId()); - } - - assertThat(actualSequence, equalTo(expectedSequence[i])); - } - - assertThat(activeHabits.indexOf(habitsArray.get(5)), equalTo(0)); - assertThat(activeHabits.indexOf(habitsArray.get(2)), equalTo(1)); - } - - @Test - public void testReorder_withInvalidArguments() throws Exception - { - Habit h1 = habitsArray.get(0); - Habit h2 = fixtures.createEmptyHabit(); - thrown.expect(IllegalArgumentException.class); - habitList.reorder(h1, h2); - } - - @Test - public void testOrder_inherit() - { - habitList.setPrimaryOrder(BY_COLOR_ASC); - HabitList filteredList = habitList.getFiltered(new HabitMatcherBuilder() - .setArchivedAllowed(false) - .setCompletedAllowed(false) - .build()); - assertEquals(filteredList.getPrimaryOrder(), BY_COLOR_ASC); - } - - @Test - public void testWriteCSV() throws IOException - { - HabitList list = modelFactory.buildHabitList(); - - Habit h1 = fixtures.createEmptyHabit(); - h1.setName("Meditate"); - h1.setQuestion("Did you meditate this morning?"); - h1.setDescription("this is a test description"); - h1.setFrequency(Frequency.DAILY); - h1.setColor(new PaletteColor(3)); - - Habit h2 = fixtures.createEmptyHabit(); - h2.setName("Wake up early"); - h2.setQuestion("Did you wake up before 6am?"); - h2.setDescription(""); - h2.setFrequency(new Frequency(2, 3)); - h2.setColor(new PaletteColor(5)); - - list.add(h1); - list.add(h2); - - String expectedCSV = - "Position,Name,Question,Description,NumRepetitions,Interval,Color\n" + - "001,Meditate,Did you meditate this morning?,this is a test description,1,1,#FF8F00\n" + - "002,Wake up early,Did you wake up before 6am?,,2,3,#AFB42B\n"; - - StringWriter writer = new StringWriter(); - list.writeCSV(writer); - - assertThat(writer.toString(), equalTo(expectedCSV)); - } - - @Test - public void testAdd() throws Exception - { - Habit h1 = fixtures.createEmptyHabit(); - assertFalse(h1.isArchived()); - assertNull(h1.getId()); - assertThat(habitList.indexOf(h1), equalTo(-1)); - - habitList.add(h1); - assertNotNull(h1.getId()); - assertThat(habitList.indexOf(h1), not(equalTo(-1))); - assertThat(activeHabits.indexOf(h1), not(equalTo(-1))); - } - - @Test - public void testAdd_withFilteredList() throws Exception - { - thrown.expect(IllegalStateException.class); - activeHabits.add(fixtures.createEmptyHabit()); - } - - @Test - public void testRemove_onFilteredList() throws Exception - { - thrown.expect(IllegalStateException.class); - activeHabits.remove(fixtures.createEmptyHabit()); - } - - @Test - public void testReorder_onFilteredList() throws Exception - { - Habit h1 = fixtures.createEmptyHabit(); - Habit h2 = fixtures.createEmptyHabit(); - thrown.expect(IllegalStateException.class); - activeHabits.reorder(h1, h2); - } - - @Test - public void testReorder_onSortedList() throws Exception - { - habitList.setPrimaryOrder(BY_SCORE_DESC); - Habit h1 = habitsArray.get(1); - Habit h2 = habitsArray.get(2); - thrown.expect(IllegalStateException.class); - habitList.reorder(h1, h2); - } -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitListTest.kt new file mode 100644 index 000000000..7f67d3adf --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitListTest.kt @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models + +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertNull +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.CoreMatchers.not +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import java.io.IOException +import java.io.StringWriter +import java.util.ArrayList + +class HabitListTest : BaseUnitTest() { + @get:Rule + var thrown = ExpectedException.none()!! + private lateinit var habitsArray: ArrayList + private lateinit var activeHabits: HabitList + private lateinit var reminderHabits: HabitList + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habitsArray = ArrayList() + for (i in 0..9) { + val habit = fixtures.createEmptyHabit() + habitList.add(habit) + habitsArray.add(habit) + if (i % 3 == 0) habit.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + } + habitsArray[0].isArchived = true + habitsArray[1].isArchived = true + habitsArray[4].isArchived = true + habitsArray[7].isArchived = true + activeHabits = habitList.getFiltered(HabitMatcherBuilder().build()) + reminderHabits = habitList.getFiltered( + HabitMatcherBuilder() + .setArchivedAllowed(true) + .setReminderRequired(true) + .build() + ) + } + + @Test + fun testSize() { + assertThat(habitList.size(), equalTo(10)) + assertThat(activeHabits.size(), equalTo(6)) + assertThat(reminderHabits.size(), equalTo(4)) + } + + @Test + fun testGetByPosition() { + assertThat(habitList.getByPosition(0), equalTo(habitsArray[0])) + assertThat(habitList.getByPosition(3), equalTo(habitsArray[3])) + assertThat(habitList.getByPosition(9), equalTo(habitsArray[9])) + assertThat(activeHabits.getByPosition(0), equalTo(habitsArray[2])) + assertThat(reminderHabits.getByPosition(1), equalTo(habitsArray[3])) + } + + @Test + fun testGetById() { + val habit1 = habitsArray[0] + val habit2 = habitList.getById(habit1.id!!) + assertThat(habit1, equalTo(habit2)) + } + + @Test + fun testGetById_withInvalidId() { + assertNull(habitList.getById(100L)) + } + + @Test + fun testOrdering() { + val h1 = fixtures.createEmptyHabit("A Habit", PaletteColor(2), 1) + val h2 = fixtures.createEmptyHabit("B Habit", PaletteColor(2), 3) + val h3 = fixtures.createEmptyHabit("C Habit", PaletteColor(0), 0) + val h4 = fixtures.createEmptyHabit("D Habit", PaletteColor(1), 2) + + val list = modelFactory.buildHabitList().apply { + add(h3) + add(h1) + add(h4) + add(h2) + } + + list.primaryOrder = HabitList.Order.BY_POSITION + assertThat(list.getByPosition(0), equalTo(h3)) + assertThat(list.getByPosition(1), equalTo(h1)) + assertThat(list.getByPosition(2), equalTo(h4)) + assertThat(list.getByPosition(3), equalTo(h2)) + list.primaryOrder = HabitList.Order.BY_NAME_DESC + assertThat(list.getByPosition(0), equalTo(h4)) + assertThat(list.getByPosition(1), equalTo(h3)) + assertThat(list.getByPosition(2), equalTo(h2)) + assertThat(list.getByPosition(3), equalTo(h1)) + list.primaryOrder = HabitList.Order.BY_NAME_ASC + assertThat(list.getByPosition(0), equalTo(h1)) + assertThat(list.getByPosition(1), equalTo(h2)) + assertThat(list.getByPosition(2), equalTo(h3)) + assertThat(list.getByPosition(3), equalTo(h4)) + list.primaryOrder = HabitList.Order.BY_NAME_ASC + list.remove(h1) + list.add(h1) + assertThat(list.getByPosition(0), equalTo(h1)) + list.primaryOrder = HabitList.Order.BY_COLOR_ASC + list.secondaryOrder = HabitList.Order.BY_NAME_ASC + assertThat(list.getByPosition(0), equalTo(h3)) + assertThat(list.getByPosition(1), equalTo(h4)) + assertThat(list.getByPosition(2), equalTo(h1)) + assertThat(list.getByPosition(3), equalTo(h2)) + list.primaryOrder = HabitList.Order.BY_COLOR_DESC + list.secondaryOrder = HabitList.Order.BY_NAME_ASC + assertThat(list.getByPosition(0), equalTo(h1)) + assertThat(list.getByPosition(1), equalTo(h2)) + assertThat(list.getByPosition(2), equalTo(h4)) + assertThat(list.getByPosition(3), equalTo(h3)) + list.primaryOrder = HabitList.Order.BY_POSITION + assertThat(list.getByPosition(0), equalTo(h3)) + assertThat(list.getByPosition(1), equalTo(h1)) + assertThat(list.getByPosition(2), equalTo(h4)) + assertThat(list.getByPosition(3), equalTo(h2)) + } + + @Test + fun testReorder() { + val operations = + arrayOf(intArrayOf(5, 2), intArrayOf(3, 7), intArrayOf(4, 4), intArrayOf(8, 3)) + val expectedSequence = arrayOf( + intArrayOf(0, 1, 5, 2, 3, 4, 6, 7, 8, 9), + intArrayOf(0, 1, 5, 2, 4, 6, 7, 3, 8, 9), + intArrayOf(0, 1, 5, 2, 4, 6, 7, 3, 8, 9), + intArrayOf(0, 1, 5, 2, 4, 6, 7, 8, 3, 9) + ) + for (i in operations.indices) { + val fromHabit = habitsArray[operations[i][0]] + val toHabit = habitsArray[operations[i][1]] + habitList.reorder(fromHabit, toHabit) + val actualSequence = IntArray(10) + for (j in 0..9) { + val habit = habitList.getByPosition(j) + assertThat(habit.position, equalTo(j)) + actualSequence[j] = Math.toIntExact(habit.id!!) + } + assertThat(actualSequence, equalTo(expectedSequence[i])) + } + assertThat(activeHabits.indexOf(habitsArray[5]), equalTo(0)) + assertThat(activeHabits.indexOf(habitsArray[2]), equalTo(1)) + } + + @Test + @Throws(Exception::class) + fun testReorder_withInvalidArguments() { + val h1 = habitsArray[0] + val h2 = fixtures.createEmptyHabit() + thrown.expect(IllegalArgumentException::class.java) + habitList.reorder(h1, h2) + } + + @Test + fun testOrder_inherit() { + habitList.primaryOrder = HabitList.Order.BY_COLOR_ASC + val filteredList = habitList.getFiltered( + HabitMatcherBuilder() + .setArchivedAllowed(false) + .setCompletedAllowed(false) + .build() + ) + assertEquals(filteredList.primaryOrder, HabitList.Order.BY_COLOR_ASC) + } + + @Test + @Throws(IOException::class) + fun testWriteCSV() { + val list = modelFactory.buildHabitList() + val h1 = fixtures.createEmptyHabit() + h1.name = "Meditate" + h1.question = "Did you meditate this morning?" + h1.description = "this is a test description" + h1.frequency = Frequency.DAILY + h1.color = PaletteColor(3) + val h2 = fixtures.createEmptyHabit() + h2.name = "Wake up early" + h2.question = "Did you wake up before 6am?" + h2.description = "" + h2.frequency = Frequency(2, 3) + h2.color = PaletteColor(5) + list.add(h1) + list.add(h2) + val expectedCSV = + """ + Position,Name,Question,Description,NumRepetitions,Interval,Color + 001,Meditate,Did you meditate this morning?,this is a test description,1,1,#FF8F00 + 002,Wake up early,Did you wake up before 6am?,,2,3,#AFB42B + + """.trimIndent() + val writer = StringWriter() + list.writeCSV(writer) + assertThat(writer.toString(), equalTo(expectedCSV)) + } + + @Test + @Throws(Exception::class) + fun testAdd() { + val h1 = fixtures.createEmptyHabit() + assertFalse(h1.isArchived) + assertNull(h1.id) + assertThat(habitList.indexOf(h1), equalTo(-1)) + habitList.add(h1) + h1.id!! + assertThat(habitList.indexOf(h1), not(equalTo(-1))) + assertThat(activeHabits.indexOf(h1), not(equalTo(-1))) + } + + @Test + @Throws(Exception::class) + fun testAdd_withFilteredList() { + thrown.expect(IllegalStateException::class.java) + activeHabits.add(fixtures.createEmptyHabit()) + } + + @Test + @Throws(Exception::class) + fun testRemove_onFilteredList() { + thrown.expect(IllegalStateException::class.java) + activeHabits.remove(fixtures.createEmptyHabit()) + } + + @Test + @Throws(Exception::class) + fun testReorder_onFilteredList() { + val h1 = fixtures.createEmptyHabit() + val h2 = fixtures.createEmptyHabit() + thrown.expect(IllegalStateException::class.java) + activeHabits.reorder(h1, h2) + } + + @Test + @Throws(Exception::class) + fun testReorder_onSortedList() { + habitList.primaryOrder = HabitList.Order.BY_SCORE_DESC + val h1 = habitsArray[1] + val h2 = habitsArray[2] + thrown.expect(IllegalStateException::class.java) + habitList.reorder(h1, h2) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitTest.java deleted file mode 100644 index bf607808c..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models; - -import org.isoron.uhabits.core.*; -import org.junit.*; -import org.junit.rules.*; - -import nl.jqno.equalsverifier.*; - -import static org.hamcrest.CoreMatchers.*; -import static org.isoron.uhabits.core.utils.DateUtils.*; -import static org.junit.Assert.*; - -public class HabitTest extends BaseUnitTest -{ - @Rule - public final ExpectedException exception = ExpectedException.none(); - - @Override - public void setUp() throws Exception - { - super.setUp(); - } - - @Test - public void testUuidGeneration() - { - Habit habit1 = modelFactory.buildHabit(); - Habit habit2 = modelFactory.buildHabit(); - assertNotNull(habit1.getUuid()); - assertNotNull(habit2.getUuid()); - assertNotEquals(habit1.getUuid(), habit2.getUuid()); - } - - @Test - public void test_copyAttributes() - { - Habit model = modelFactory.buildHabit(); - model.setArchived(true); - model.setColor(new PaletteColor(0)); - model.setFrequency(new Frequency(10, 20)); - model.setReminder(new Reminder(8, 30, new WeekdayList(1))); - - Habit habit = modelFactory.buildHabit(); - habit.copyFrom(model); - assertThat(habit.isArchived(), is(model.isArchived())); - assertThat(habit.getColor(), is(model.getColor())); - assertThat(habit.getFrequency(), equalTo(model.getFrequency())); - assertThat(habit.getReminder(), equalTo(model.getReminder())); - } - - - @Test - public void test_hasReminder() - { - Habit h = modelFactory.buildHabit(); - assertThat(h.hasReminder(), is(false)); - - h.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - assertThat(h.hasReminder(), is(true)); - } - - @Test - public void test_isCompleted() throws Exception - { - Habit h = modelFactory.buildHabit(); - assertFalse(h.isCompletedToday()); - h.getOriginalEntries().add(new Entry(getToday(), Entry.YES_MANUAL)); - h.recompute(); - assertTrue(h.isCompletedToday()); - } - - @Test - public void test_isCompleted_numerical() throws Exception - { - Habit h = modelFactory.buildHabit(); - h.setType(Habit.NUMBER_HABIT); - h.setTargetType(Habit.AT_LEAST); - h.setTargetValue(100.0); - assertFalse(h.isCompletedToday()); - - h.getOriginalEntries().add(new Entry(getToday(), 200_000)); - h.recompute(); - assertTrue(h.isCompletedToday()); - - h.getOriginalEntries().add(new Entry(getToday(), 100_000)); - h.recompute(); - assertTrue(h.isCompletedToday()); - - h.getOriginalEntries().add(new Entry(getToday(), 50_000)); - h.recompute(); - assertFalse(h.isCompletedToday()); - - h.setTargetType(Habit.AT_MOST); - h.getOriginalEntries().add(new Entry(getToday(), 200_000)); - h.recompute(); - assertFalse(h.isCompletedToday()); - - h.getOriginalEntries().add(new Entry(getToday(), 100_000)); - h.recompute(); - assertTrue(h.isCompletedToday()); - - h.getOriginalEntries().add(new Entry(getToday(), 50_000)); - h.recompute(); - assertTrue(h.isCompletedToday()); - } - - @Test - public void testURI() throws Exception - { - assertTrue(habitList.isEmpty()); - Habit h = modelFactory.buildHabit(); - habitList.add(h); - assertThat(h.getId(), equalTo(0L)); - assertThat(h.getUriString(), - equalTo("content://org.isoron.uhabits/habit/0")); - } -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitTest.kt new file mode 100644 index 000000000..d13c8468e --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/HabitTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models + +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.core.IsEqual.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException + +class HabitTest : BaseUnitTest() { + @get:Rule + val exception = ExpectedException.none()!! + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + } + + @Test + fun testUuidGeneration() { + val uuid1 = modelFactory.buildHabit().uuid!! + val uuid2 = modelFactory.buildHabit().uuid!! + assertNotEquals(uuid1, uuid2) + } + + @Test + fun test_copyAttributes() { + val model = modelFactory.buildHabit() + model.isArchived = true + model.color = PaletteColor(0) + model.frequency = Frequency(10, 20) + model.reminder = Reminder(8, 30, WeekdayList(1)) + val habit = modelFactory.buildHabit() + habit.copyFrom(model) + assertTrue(habit.isArchived == model.isArchived) + assertThat(habit.isArchived, `is`(model.isArchived)) + assertThat(habit.color, `is`(model.color)) + assertThat(habit.frequency, equalTo(model.frequency)) + assertThat(habit.reminder, equalTo(model.reminder)) + } + + @Test + fun test_hasReminder() { + val h = modelFactory.buildHabit() + assertThat(h.hasReminder(), `is`(false)) + h.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + assertThat(h.hasReminder(), `is`(true)) + } + + @Test + @Throws(Exception::class) + fun test_isCompleted() { + val h = modelFactory.buildHabit() + assertFalse(h.isCompletedToday()) + h.originalEntries.add(Entry(getToday(), Entry.YES_MANUAL)) + h.recompute() + assertTrue(h.isCompletedToday()) + } + + @Test + @Throws(Exception::class) + fun test_isCompleted_numerical() { + val h = modelFactory.buildHabit() + h.type = Habit.NUMBER_HABIT + h.targetType = Habit.AT_LEAST + h.targetValue = 100.0 + assertFalse(h.isCompletedToday()) + h.originalEntries.add(Entry(getToday(), 200000)) + h.recompute() + assertTrue(h.isCompletedToday()) + h.originalEntries.add(Entry(getToday(), 100000)) + h.recompute() + assertTrue(h.isCompletedToday()) + h.originalEntries.add(Entry(getToday(), 50000)) + h.recompute() + assertFalse(h.isCompletedToday()) + h.targetType = Habit.AT_MOST + h.originalEntries.add(Entry(getToday(), 200000)) + h.recompute() + assertFalse(h.isCompletedToday()) + h.originalEntries.add(Entry(getToday(), 100000)) + h.recompute() + assertTrue(h.isCompletedToday()) + h.originalEntries.add(Entry(getToday(), 50000)) + h.recompute() + assertTrue(h.isCompletedToday()) + } + + @Test + @Throws(Exception::class) + fun testURI() { + assertTrue(habitList.isEmpty) + val h = modelFactory.buildHabit() + habitList.add(h) + assertThat(h.id, equalTo(0L)) + assertThat(h.uriString, equalTo("content://org.isoron.uhabits/habit/0")) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.java deleted file mode 100644 index 7c885da6f..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import java.io.*; -import java.util.*; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.core.IsEqual.*; -import static org.hamcrest.number.IsCloseTo.*; -import static org.hamcrest.number.OrderingComparison.*; -import static org.isoron.uhabits.core.models.Entry.*; - -public class ScoreListTest extends BaseUnitTest -{ - private static final double E = 1e-6; - - private Habit habit; - - private Timestamp today; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - today = DateUtils.getToday(); - habit = fixtures.createEmptyHabit(); - } - - @Test - public void test_getValue() - { - check(0, 20); - - double[] expectedValues = { - 0.655747, - 0.636894, - 0.617008, - 0.596033, - 0.573910, - 0.550574, - 0.525961, - 0.500000, - 0.472617, - 0.443734, - 0.413270, - 0.381137, - 0.347244, - 0.311495, - 0.273788, - 0.234017, - 0.192067, - 0.147820, - 0.101149, - 0.051922, - 0.000000, - 0.000000, - 0.000000 - }; - - checkScoreValues(expectedValues); - } - - @Test - public void test_getValueWithSkip() - { - check(0, 20); - addSkip(5); - addSkip(10); - addSkip(11); - habit.recompute(); - - double expectedValues[] = { - 0.596033, - 0.573910, - 0.550574, - 0.525961, - 0.500000, - 0.472617, - 0.472617, - 0.443734, - 0.413270, - 0.381137, - 0.347244, - 0.347244, - 0.347244, - 0.311495, - 0.273788, - 0.234017, - 0.192067, - 0.147820, - 0.101149, - 0.051922, - 0.000000, - 0.000000, - 0.000000 - }; - - checkScoreValues(expectedValues); - } - - @Test - public void test_getValueWithSkip2() - { - check(5); - addSkip(4); - habit.recompute(); - - double[] expectedValues = { - 0.041949, - 0.044247, - 0.046670, - 0.049226, - 0.051922, - 0.051922, - 0.0 - }; - - checkScoreValues(expectedValues); - } - - @Test - public void test_imperfectNonDaily() - { - // If the habit should be performed 3 times per week and the user misses 1 repetition - // each week, score should converge to 66%. - habit.setFrequency(new Frequency(3, 7)); - ArrayList values = new ArrayList<>(); - for (int k = 0; k < 100; k++) - { - values.add(YES_MANUAL); - values.add(YES_MANUAL); - values.add(NO); - values.add(NO); - values.add(NO); - values.add(NO); - values.add(NO); - } - check(values); - assertThat(habit.getScores().get(today).getValue(), closeTo(2/3.0, E)); - - // Missing 2 repetitions out of 4 per week, the score should converge to 50% - habit.setFrequency(new Frequency(4, 7)); - habit.recompute(); - assertThat(habit.getScores().get(today).getValue(), closeTo(0.5, E)); - } - - @Test - public void test_irregularNonDaily() - { - // If the user performs habit perfectly each week, but on different weekdays, - // score should still converge to 100% - habit.setFrequency(new Frequency(1, 7)); - ArrayList values = new ArrayList<>(); - for (int k = 0; k < 100; k++) - { - // Week 0 - values.add(YES_MANUAL); - values.add(NO); - values.add(NO); - values.add(NO); - values.add(NO); - values.add(NO); - values.add(NO); - - // Week 1 - values.add(NO); - values.add(NO); - values.add(NO); - values.add(NO); - values.add(NO); - values.add(NO); - values.add(YES_MANUAL); - } - check(values); - assertThat(habit.getScores().get(today).getValue(), closeTo(1.0, 1e-3)); - } - - @Test - public void shouldAchieveHighScoreInReasonableTime() - { - // Daily habits should achieve at least 99% in 3 months - habit = fixtures.createEmptyHabit(); - habit.setFrequency(Frequency.DAILY); - for (int i = 0; i < 90; i++) check(i); - habit.recompute(); - assertThat(habit.getScores().get(today).getValue(), greaterThan(0.99)); - - // Weekly habits should achieve at least 99% in 9 months - habit = fixtures.createEmptyHabit(); - habit.setFrequency(Frequency.WEEKLY); - for (int i = 0; i < 39; i++) check(7 * i); - habit.recompute(); - assertThat(habit.getScores().get(today).getValue(), greaterThan(0.99)); - - // Monthly habits should achieve at least 99% in 18 months - habit.setFrequency(new Frequency(1, 30)); - for (int i = 0; i < 18; i++) check(30 * i); - habit.recompute(); - assertThat(habit.getScores().get(today).getValue(), greaterThan(0.99)); - } - - @Test - public void test_recompute() - { - assertThat(habit.getScores().get(today).getValue(), closeTo(0.0, E)); - - check(0, 2); - assertThat(habit.getScores().get(today).getValue(), closeTo(0.101149, E)); - - habit.setFrequency(new Frequency(1, 2)); - habit.recompute(); - - assertThat(habit.getScores().get(today).getValue(), closeTo(0.054816, E)); - } - - @Test - public void test_addThenRemove() - { - Habit habit = fixtures.createEmptyHabit(); - habit.recompute(); - assertThat(habit.getScores().get(today).getValue(), closeTo(0.0, E)); - - habit.getOriginalEntries().add(new Entry(today, YES_MANUAL)); - habit.recompute(); - assertThat(habit.getScores().get(today).getValue(), closeTo(0.051922, E)); - - habit.getOriginalEntries().add(new Entry(today, UNKNOWN)); - habit.recompute(); - assertThat(habit.getScores().get(today).getValue(), closeTo(0.0, E)); - } - - private void check(final int offset) - { - EntryList entries = habit.getOriginalEntries(); - entries.add(new Entry(today.minus(offset), YES_MANUAL)); - } - - private void check(final int from, final int to) - { - EntryList entries = habit.getOriginalEntries(); - for (int i = from; i < to; i++) - entries.add(new Entry(today.minus(i), YES_MANUAL)); - habit.recompute(); - } - - private void check(ArrayList values) - { - EntryList entries = habit.getOriginalEntries(); - for (int i = 0; i < values.size(); i++) - if (values.get(i) == YES_MANUAL) - entries.add(new Entry(today.minus(i), YES_MANUAL)); - habit.recompute(); - } - - private void addSkip(final int day) - { - EntryList entries = habit.getOriginalEntries(); - entries.add(new Entry(today.minus(day), Entry.SKIP)); - } - - private void checkScoreValues(double[] expectedValues) - { - Timestamp current = today; - ScoreList scores = habit.getScores(); - for (double expectedValue : expectedValues) - { - assertThat(scores.get(current).getValue(), closeTo(expectedValue, E)); - current = current.minus(1); - } - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt new file mode 100644 index 000000000..c96d2ce46 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreListTest.kt @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.number.IsCloseTo +import org.hamcrest.number.OrderingComparison +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.junit.Before +import org.junit.Test +import java.util.ArrayList + +class ScoreListTest : BaseUnitTest() { + private lateinit var habit: Habit + private lateinit var today: Timestamp + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + today = getToday() + habit = fixtures.createEmptyHabit() + } + + @Test + fun test_getValue() { + check(0, 20) + val expectedValues = doubleArrayOf( + 0.655747, + 0.636894, + 0.617008, + 0.596033, + 0.573910, + 0.550574, + 0.525961, + 0.500000, + 0.472617, + 0.443734, + 0.413270, + 0.381137, + 0.347244, + 0.311495, + 0.273788, + 0.234017, + 0.192067, + 0.147820, + 0.101149, + 0.051922, + 0.000000, + 0.000000, + 0.000000 + ) + checkScoreValues(expectedValues) + } + + @Test + fun test_getValueWithSkip() { + check(0, 20) + addSkip(5) + addSkip(10) + addSkip(11) + habit.recompute() + val expectedValues = doubleArrayOf( + 0.596033, + 0.573910, + 0.550574, + 0.525961, + 0.500000, + 0.472617, + 0.472617, + 0.443734, + 0.413270, + 0.381137, + 0.347244, + 0.347244, + 0.347244, + 0.311495, + 0.273788, + 0.234017, + 0.192067, + 0.147820, + 0.101149, + 0.051922, + 0.000000, + 0.000000, + 0.000000 + ) + checkScoreValues(expectedValues) + } + + @Test + fun test_getValueWithSkip2() { + check(5) + addSkip(4) + habit.recompute() + val expectedValues = doubleArrayOf( + 0.041949, + 0.044247, + 0.046670, + 0.049226, + 0.051922, + 0.051922, + 0.0 + ) + checkScoreValues(expectedValues) + } + + @Test + fun test_imperfectNonDaily() { + // If the habit should be performed 3 times per week and the user misses 1 repetition + // each week, score should converge to 66%. + habit.frequency = Frequency(3, 7) + val values = ArrayList() + for (k in 0..99) { + values.add(Entry.YES_MANUAL) + values.add(Entry.YES_MANUAL) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + } + check(values) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(2 / 3.0, E)) + + // Missing 2 repetitions out of 4 per week, the score should converge to 50% + habit.frequency = Frequency(4, 7) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.5, E)) + } + + @Test + fun test_irregularNonDaily() { + // If the user performs habit perfectly each week, but on different weekdays, + // score should still converge to 100% + habit.frequency = Frequency(1, 7) + val values = ArrayList() + for (k in 0..99) { + // Week 0 + values.add(Entry.YES_MANUAL) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + + // Week 1 + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.NO) + values.add(Entry.YES_MANUAL) + } + check(values) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(1.0, 1e-3)) + } + + @Test + fun shouldAchieveHighScoreInReasonableTime() { + // Daily habits should achieve at least 99% in 3 months + habit = fixtures.createEmptyHabit() + habit.frequency = Frequency.DAILY + for (i in 0..89) check(i) + habit.recompute() + assertThat(habit.scores[today].value, OrderingComparison.greaterThan(0.99)) + + // Weekly habits should achieve at least 99% in 9 months + habit = fixtures.createEmptyHabit() + habit.frequency = Frequency.WEEKLY + for (i in 0..38) check(7 * i) + habit.recompute() + assertThat(habit.scores[today].value, OrderingComparison.greaterThan(0.99)) + + // Monthly habits should achieve at least 99% in 18 months + habit.frequency = Frequency(1, 30) + for (i in 0..17) check(30 * i) + habit.recompute() + assertThat(habit.scores[today].value, OrderingComparison.greaterThan(0.99)) + } + + @Test + fun test_recompute() { + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.0, E)) + check(0, 2) + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.101149, E)) + habit.frequency = Frequency(1, 2) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.054816, E)) + } + + @Test + fun test_addThenRemove() { + val habit = fixtures.createEmptyHabit() + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.0, E)) + habit.originalEntries.add(Entry(today, Entry.YES_MANUAL)) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.051922, E)) + habit.originalEntries.add(Entry(today, Entry.UNKNOWN)) + habit.recompute() + assertThat(habit.scores[today].value, IsCloseTo.closeTo(0.0, E)) + } + + private fun check(offset: Int) { + val entries = habit.originalEntries + entries.add(Entry(today.minus(offset), Entry.YES_MANUAL)) + } + + private fun check(from: Int, to: Int) { + val entries = habit.originalEntries + for (i in from until to) entries.add(Entry(today.minus(i), Entry.YES_MANUAL)) + habit.recompute() + } + + private fun check(values: ArrayList) { + val entries = habit.originalEntries + for (i in values.indices) if (values[i] == Entry.YES_MANUAL) entries.add( + Entry( + today.minus(i), + Entry.YES_MANUAL + ) + ) + habit.recompute() + } + + private fun addSkip(day: Int) { + val entries = habit.originalEntries + entries.add(Entry(today.minus(day), Entry.SKIP)) + } + + private fun checkScoreValues(expectedValues: DoubleArray) { + var current = today + val scores = habit.scores + for (expectedValue in expectedValues) { + assertThat(scores[current].value, IsCloseTo.closeTo(expectedValue, E)) + current = current.minus(1) + } + } + + companion object { + private const val E = 1e-6 + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreTest.java deleted file mode 100644 index 49041cc2c..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models; - -import org.isoron.uhabits.core.*; -import org.junit.*; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.core.IsEqual.*; -import static org.hamcrest.number.IsCloseTo.*; -import static org.isoron.uhabits.core.models.Score.*; - - -public class ScoreTest extends BaseUnitTest -{ - private static final double E = 1e-6; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - } - - @Test - public void test_compute_withDailyHabit() - { - int check = 1; - double freq = 1.0; - assertThat(compute(freq, 0, check), closeTo(0.051922, E)); - assertThat(compute(freq, 0.5, check), closeTo(0.525961, E)); - assertThat(compute(freq, 0.75, check), closeTo(0.762981, E)); - - check = 0; - assertThat(compute(freq, 0, check), closeTo(0, E)); - assertThat(compute(freq, 0.5, check), closeTo(0.474039, E)); - assertThat(compute(freq, 0.75, check), closeTo(0.711058, E)); - } - - @Test - public void test_compute_withNonDailyHabit() - { - int check = 1; - double freq = 1 / 3.0; - assertThat(compute(freq, 0, check), closeTo(0.030314, E)); - assertThat(compute(freq, 0.5, check), closeTo(0.515157, E)); - assertThat(compute(freq, 0.75, check), closeTo(0.757578, E)); - - check = 0; - assertThat(compute(freq, 0, check), closeTo(0.0, E)); - assertThat(compute(freq, 0.5, check), closeTo(0.484842, E)); - assertThat(compute(freq, 0.75, check), closeTo(0.727263, E)); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreTest.kt new file mode 100644 index 000000000..329d34656 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/ScoreTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.number.IsCloseTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Score.Companion.compute +import org.junit.Before +import org.junit.Test + +class ScoreTest : BaseUnitTest() { + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + } + + @Test + fun test_compute_withDailyHabit() { + var check = 1 + val freq = 1.0 + assertThat(compute(freq, 0.0, check.toDouble()), IsCloseTo.closeTo(0.051922, E)) + assertThat(compute(freq, 0.5, check.toDouble()), IsCloseTo.closeTo(0.525961, E)) + assertThat(compute(freq, 0.75, check.toDouble()), IsCloseTo.closeTo(0.762981, E)) + check = 0 + assertThat(compute(freq, 0.0, check.toDouble()), IsCloseTo.closeTo(0.0, E)) + assertThat(compute(freq, 0.5, check.toDouble()), IsCloseTo.closeTo(0.474039, E)) + assertThat(compute(freq, 0.75, check.toDouble()), IsCloseTo.closeTo(0.711058, E)) + } + + @Test + fun test_compute_withNonDailyHabit() { + var check = 1 + val freq = 1 / 3.0 + assertThat(compute(freq, 0.0, check.toDouble()), IsCloseTo.closeTo(0.030314, E)) + assertThat(compute(freq, 0.5, check.toDouble()), IsCloseTo.closeTo(0.515157, E)) + assertThat(compute(freq, 0.75, check.toDouble()), IsCloseTo.closeTo(0.757578, E)) + check = 0 + assertThat(compute(freq, 0.0, check.toDouble()), IsCloseTo.closeTo(0.0, E)) + assertThat(compute(freq, 0.5, check.toDouble()), IsCloseTo.closeTo(0.484842, E)) + assertThat(compute(freq, 0.75, check.toDouble()), IsCloseTo.closeTo(0.727263, E)) + } + + companion object { + private const val E = 1e-6 + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/StreakListTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/StreakListTest.java deleted file mode 100644 index fce357bcb..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/StreakListTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import java.util.*; - -import static junit.framework.TestCase.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.core.IsEqual.*; -import static org.mockito.Mockito.*; - -public class StreakListTest extends BaseUnitTest -{ - private Habit habit; - - private StreakList streaks; - - private Timestamp today; - - @Override - public void setUp() throws Exception - { - super.setUp(); - habit = fixtures.createLongHabit(); - habit.setFrequency(Frequency.DAILY); - habit.recompute(); - - streaks = habit.getStreaks(); - today = DateUtils.getToday(); - } - - @Test - public void testGetBest() throws Exception - { - List best = streaks.getBest(4); - assertThat(best.size(), equalTo(4)); - assertThat(best.get(0).getLength(), equalTo(4)); - assertThat(best.get(1).getLength(), equalTo(3)); - assertThat(best.get(2).getLength(), equalTo(5)); - assertThat(best.get(3).getLength(), equalTo(6)); - - best = streaks.getBest(2); - assertThat(best.size(), equalTo(2)); - assertThat(best.get(0).getLength(), equalTo(5)); - assertThat(best.get(1).getLength(), equalTo(6)); - } - - @Test - public void testGetBest_withUnknowns() - { - habit.getOriginalEntries().clear(); - habit.getOriginalEntries().add(new Entry(today, Entry.YES_MANUAL)); - habit.getOriginalEntries().add(new Entry(today.minus(5), Entry.NO)); - habit.recompute(); - - List best = streaks.getBest(5); - assertThat(best.size(), equalTo(1)); - assertThat(best.get(0).getLength(), equalTo(1)); - } -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/StreakListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/StreakListTest.kt new file mode 100644 index 000000000..06575a406 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/StreakListTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models + +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.junit.Test + +class StreakListTest : BaseUnitTest() { + private lateinit var habit: Habit + private lateinit var streaks: StreakList + private lateinit var today: Timestamp + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createLongHabit() + habit.frequency = Frequency.DAILY + habit.recompute() + streaks = habit.streaks + today = getToday() + } + + @Test + @Throws(Exception::class) + fun testGetBest() { + var best = streaks.getBest(4) + assertThat(best.size, equalTo(4)) + assertThat(best[0].length, equalTo(4)) + assertThat(best[1].length, equalTo(3)) + assertThat(best[2].length, equalTo(5)) + assertThat(best[3].length, equalTo(6)) + best = streaks.getBest(2) + assertThat(best.size, equalTo(2)) + assertThat(best[0].length, equalTo(5)) + assertThat(best[1].length, equalTo(6)) + } + + @Test + fun testGetBest_withUnknowns() { + habit.originalEntries.clear() + habit.originalEntries.add(Entry(today, Entry.YES_MANUAL)) + habit.originalEntries.add(Entry(today.minus(5), Entry.NO)) + habit.recompute() + val best = streaks.getBest(5) + assertThat(best.size, equalTo(1)) + assertThat(best[0].length, equalTo(1)) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/TimestampTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/TimestampTest.java deleted file mode 100644 index f8e9130aa..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/TimestampTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import static junit.framework.TestCase.assertFalse; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertTrue; - -public class TimestampTest extends BaseUnitTest -{ - @Test - public void testCompare() throws Exception - { - Timestamp t1 = DateUtils.getToday(); - Timestamp t2 = t1.minus(1); - Timestamp t3 = t1.plus(3); - - assertThat(t1.compareTo(t2), greaterThan(0)); - assertThat(t1.compareTo(t1), equalTo(0)); - assertThat(t1.compareTo(t3), lessThan(0)); - - assertTrue(t1.isNewerThan(t2)); - assertFalse(t1.isNewerThan(t1)); - assertFalse(t2.isNewerThan(t1)); - - assertTrue(t2.isOlderThan(t1)); - assertFalse(t1.isOlderThan(t2)); - } - - @Test - public void testDaysUntil() throws Exception - { - Timestamp t = DateUtils.getToday(); - assertThat(t.daysUntil(t), equalTo(0)); - - assertThat(t.daysUntil(t.plus(1)), equalTo(1)); - assertThat(t.daysUntil(t.plus(3)), equalTo(3)); - assertThat(t.daysUntil(t.plus(300)), equalTo(300)); - - assertThat(t.daysUntil(t.minus(1)), equalTo(-1)); - assertThat(t.daysUntil(t.minus(3)), equalTo(-3)); - assertThat(t.daysUntil(t.minus(300)), equalTo(-300)); - } - - @Test - public void testInexact() throws Exception - { - Timestamp t = new Timestamp(1578054764000L); - assertThat(t.getUnixTime(), equalTo(1578009600000L)); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/TimestampTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/TimestampTest.kt new file mode 100644 index 000000000..8296528b7 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/TimestampTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models + +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.greaterThan +import org.hamcrest.Matchers.lessThan +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.junit.Test + +class TimestampTest : BaseUnitTest() { + @Test + @Throws(Exception::class) + fun testCompare() { + val t1 = getToday() + val t2 = t1.minus(1) + val t3 = t1.plus(3) + assertThat(t1.compareTo(t2), greaterThan(0)) + assertThat(t1.compareTo(t1), equalTo(0)) + assertThat(t1.compareTo(t3), lessThan(0)) + assertTrue(t1.isNewerThan(t2)) + assertFalse(t1.isNewerThan(t1)) + assertFalse(t2.isNewerThan(t1)) + assertTrue(t2.isOlderThan(t1)) + assertFalse(t1.isOlderThan(t2)) + } + + @Test + @Throws(Exception::class) + fun testDaysUntil() { + val t = getToday() + assertThat(t.daysUntil(t), equalTo(0)) + assertThat(t.daysUntil(t.plus(1)), equalTo(1)) + assertThat(t.daysUntil(t.plus(3)), equalTo(3)) + assertThat(t.daysUntil(t.plus(300)), equalTo(300)) + assertThat(t.daysUntil(t.minus(1)), equalTo(-1)) + assertThat(t.daysUntil(t.minus(3)), equalTo(-3)) + assertThat(t.daysUntil(t.minus(300)), equalTo(-300)) + } + + @Test + @Throws(Exception::class) + fun testInexact() { + val t = Timestamp(1578054764000L) + assertThat(t.unixTime, equalTo(1578009600000L)) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/WeekdayListTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/WeekdayListTest.java deleted file mode 100644 index c0b2c7405..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/WeekdayListTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models; - -import org.isoron.uhabits.core.*; -import org.junit.*; - -import static org.junit.Assert.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsEqual.*; - -public class WeekdayListTest extends BaseUnitTest -{ - @Test - public void test() - { - int daysInt = 124; - boolean[] daysArray = new boolean[]{ - false, false, true, true, true, true, true - }; - - WeekdayList list = new WeekdayList(daysArray); - assertThat(list.toArray(), equalTo(daysArray)); - assertThat(list.toInteger(), equalTo(daysInt)); - - list = new WeekdayList(daysInt); - assertThat(list.toArray(), equalTo(daysArray)); - assertThat(list.toInteger(), equalTo(daysInt)); - } - - @Test - public void testEmpty() - { - WeekdayList list = new WeekdayList(0); - assertTrue(list.isEmpty()); - - assertFalse(WeekdayList.EVERY_DAY.isEmpty()); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/WeekdayListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/WeekdayListTest.kt new file mode 100644 index 000000000..8bcde06fb --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/WeekdayListTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models + +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.junit.Test + +class WeekdayListTest : BaseUnitTest() { + @Test + fun test() { + val daysInt = 124 + val daysArray = booleanArrayOf(false, false, true, true, true, true, true) + var list = WeekdayList(daysArray) + assertThat(list.toArray(), equalTo(daysArray)) + assertThat(list.toInteger(), equalTo(daysInt)) + list = WeekdayList(daysInt) + assertThat(list.toArray(), equalTo(daysArray)) + assertThat(list.toInteger(), equalTo(daysInt)) + } + + @Test + fun testEmpty() { + val list = WeekdayList(0) + assertTrue(list.isEmpty) + assertFalse(WeekdayList.EVERY_DAY.isEmpty) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteEntryListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteEntryListTest.kt index 28f2aea03..3057b8993 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteEntryListTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteEntryListTest.kt @@ -20,9 +20,8 @@ package org.isoron.uhabits.core.models.sqlite import junit.framework.Assert.assertEquals -import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertNull -import org.isoron.uhabits.core.BaseUnitTest.buildMemoryDatabase +import org.isoron.uhabits.core.BaseUnitTest.Companion.buildMemoryDatabase import org.isoron.uhabits.core.database.Repository import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN @@ -87,22 +86,17 @@ class SQLiteEntryListTest { val original = Entry(today, 150) entries.add(original) - val retrieved = getByTimestamp(1, today) - assertNotNull(retrieved) - assertEquals(original, retrieved!!.toEntry()) + val retrieved = getByTimestamp(1, today)!! + assertEquals(original, retrieved.toEntry()) val replacement = Entry(today, 90) entries.add(replacement) - val retrieved2 = getByTimestamp(1, today) - assertNotNull(retrieved2) - assertEquals(replacement, retrieved2!!.toEntry()) + val retrieved2 = getByTimestamp(1, today)!! + assertEquals(replacement, retrieved2.toEntry()) } - private fun getByTimestamp( - habitId: Int, - timestamp: Timestamp, - ): EntryRecord? { + private fun getByTimestamp(habitId: Int, timestamp: Timestamp): EntryRecord? { return repository.findFirst( "where habit = ? and timestamp = ?", habitId.toString(), diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.java deleted file mode 100644 index 87ee161c4..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models.sqlite; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.database.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.models.sqlite.records.*; -import org.isoron.uhabits.core.test.*; -import org.junit.*; -import org.junit.rules.*; - -import java.util.*; - -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -public class SQLiteHabitListTest extends BaseUnitTest -{ - @Rule - public ExpectedException exception = ExpectedException.none(); - - private SQLiteHabitList habitList; - - private Repository repository; - - private ModelObservable.Listener listener; - - private ArrayList habitsArray; - - private HabitList activeHabits; - - private HabitList reminderHabits; - - @Override - public void setUp() throws Exception - { - super.setUp(); - Database db = buildMemoryDatabase(); - modelFactory = new SQLModelFactory(db); - habitList = new SQLiteHabitList(modelFactory); - fixtures = new HabitFixtures(modelFactory, habitList); - repository = new Repository<>(HabitRecord.class, db); - habitsArray = new ArrayList<>(); - - for (int i = 0; i < 10; i++) - { - Habit habit = fixtures.createEmptyHabit(); - habit.setName("habit " + (i+1)); - habitList.update(habit); - habitsArray.add(habit); - - if (i % 3 == 0) - habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - } - - habitsArray.get(0).setArchived(true); - habitsArray.get(1).setArchived(true); - habitsArray.get(4).setArchived(true); - habitsArray.get(7).setArchived(true); - habitList.update(habitsArray); - - activeHabits = habitList.getFiltered(new HabitMatcherBuilder().build()); - - reminderHabits = habitList.getFiltered(new HabitMatcherBuilder() - .setArchivedAllowed(true) - .setReminderRequired(true) - .build()); - - listener = mock(ModelObservable.Listener.class); - habitList.getObservable().addListener(listener); - } - - @Override - public void tearDown() throws Exception - { - habitList.getObservable().removeListener(listener); - super.tearDown(); - } - - @Test - public void testAdd_withDuplicate() - { - Habit habit = modelFactory.buildHabit(); - habitList.add(habit); - verify(listener).onModelChange(); - - exception.expect(IllegalArgumentException.class); - habitList.add(habit); - } - - @Test - public void testAdd_withId() - { - Habit habit = modelFactory.buildHabit(); - habit.setName("Hello world with id"); - habit.setId(12300L); - - habitList.add(habit); - assertThat(habit.getId(), equalTo(12300L)); - - HabitRecord record = repository.find(12300L); - assertNotNull(record); - assertThat(record.name, equalTo(habit.getName())); - } - - @Test - public void testAdd_withoutId() - { - Habit habit = modelFactory.buildHabit(); - habit.setName("Hello world"); - assertNull(habit.getId()); - - habitList.add(habit); - assertNotNull(habit.getId()); - - HabitRecord record = repository.find(habit.getId()); - assertNotNull(record); - assertThat(record.name, equalTo(habit.getName())); - } - - @Test - public void testSize() - { - assertThat(habitList.size(), equalTo(10)); - } - - @Test - public void testGetById() - { - Habit h1 = habitList.getById(1); - assertNotNull(h1); - assertThat(h1.getName(), equalTo("habit 1")); - - Habit h2 = habitList.getById(2); - assertNotNull(h2); - assertThat(h2, 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(4); - assertNotNull(h); - assertThat(h.getName(), equalTo("habit 5")); - } - - @Test - public void testIndexOf() - { - Habit h1 = habitList.getByPosition(5); - assertNotNull(h1); - assertThat(habitList.indexOf(h1), equalTo(5)); - - Habit h2 = modelFactory.buildHabit(); - assertThat(habitList.indexOf(h2), equalTo(-1)); - - h2.setId(1000L); - assertThat(habitList.indexOf(h2), equalTo(-1)); - } - - @Test - public void testRemove() throws Exception - { - Habit h = habitList.getById(2); - habitList.remove(h); - assertThat(habitList.indexOf(h), equalTo(-1)); - - HabitRecord rec = repository.find(2L); - assertNull(rec); - - rec = repository.find(3L); - assertNotNull(rec); - assertThat(rec.position, equalTo(1)); - } - - @Test - public void testReorder() - { - Habit habit3 = habitList.getById(3); - Habit habit4 = habitList.getById(4); - assertNotNull(habit3); - assertNotNull(habit4); - habitList.reorder(habit4, habit3); - - HabitRecord record3 = repository.find(3L); - assertNotNull(record3); - assertThat(record3.position, equalTo(3)); - - HabitRecord record4 = repository.find(4L); - assertNotNull(record4); - assertThat(record4.position, equalTo(2)); - } - - -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.kt new file mode 100644 index 000000000..07ac72e02 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/SQLiteHabitListTest.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models.sqlite + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import junit.framework.Assert.assertNull +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.database.Database +import org.isoron.uhabits.core.database.Repository +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.HabitMatcherBuilder +import org.isoron.uhabits.core.models.ModelObservable +import org.isoron.uhabits.core.models.Reminder +import org.isoron.uhabits.core.models.WeekdayList +import org.isoron.uhabits.core.models.sqlite.records.HabitRecord +import org.isoron.uhabits.core.test.HabitFixtures +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import java.util.ArrayList + +class SQLiteHabitListTest : BaseUnitTest() { + @get:Rule + var exception = ExpectedException.none()!! + private lateinit var repository: Repository + private var listener: ModelObservable.Listener = mock() + private lateinit var habitsArray: ArrayList + private lateinit var activeHabits: HabitList + private lateinit var reminderHabits: HabitList + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + val db: Database = buildMemoryDatabase() + modelFactory = SQLModelFactory(db) + habitList = SQLiteHabitList(modelFactory) + fixtures = HabitFixtures(modelFactory, habitList) + repository = Repository(HabitRecord::class.java, db) + habitsArray = ArrayList() + for (i in 0..9) { + val habit = fixtures.createEmptyHabit() + habit.name = "habit " + (i + 1) + habitList.update(habit) + habitsArray.add(habit) + if (i % 3 == 0) habit.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + } + habitsArray[0].isArchived = true + habitsArray[1].isArchived = true + habitsArray[4].isArchived = true + habitsArray[7].isArchived = true + habitList.update(habitsArray) + activeHabits = habitList.getFiltered(HabitMatcherBuilder().build()) + reminderHabits = habitList.getFiltered( + HabitMatcherBuilder() + .setArchivedAllowed(true) + .setReminderRequired(true) + .build() + ) + habitList.observable.addListener(listener) + } + + @Throws(Exception::class) + override fun tearDown() { + habitList.observable.removeListener(listener) + super.tearDown() + } + + @Test + fun testAdd_withDuplicate() { + val habit = modelFactory.buildHabit() + habitList.add(habit) + verify(listener).onModelChange() + exception.expect(IllegalArgumentException::class.java) + habitList.add(habit) + } + + @Test + fun testAdd_withId() { + val habit = modelFactory.buildHabit() + habit.name = "Hello world with id" + habit.id = 12300L + habitList.add(habit) + assertThat(habit.id, equalTo(12300L)) + val record = repository.find(12300L) + assertThat(record!!.name, equalTo(habit.name)) + } + + @Test + fun testAdd_withoutId() { + val habit = modelFactory.buildHabit() + habit.name = "Hello world" + assertNull(habit.id) + habitList.add(habit) + val record = repository.find(habit.id!!) + assertThat(record!!.name, equalTo(habit.name)) + } + + @Test + fun testSize() { + assertThat(habitList.size(), equalTo(10)) + } + + @Test + fun testGetById() { + val h1 = habitList.getById(1)!! + assertThat(h1.name, equalTo("habit 1")) + val h2 = habitList.getById(2)!! + assertThat(h2, equalTo(h2)) + } + + @Test + fun testGetById_withInvalid() { + val invalidId = 9183792001L + val h1 = habitList.getById(invalidId) + assertNull(h1) + } + + @Test + fun testGetByPosition() { + val h = habitList.getByPosition(4) + assertThat(h.name, equalTo("habit 5")) + } + + @Test + fun testIndexOf() { + val h1 = habitList.getByPosition(5) + assertThat(habitList.indexOf(h1), equalTo(5)) + val h2 = modelFactory.buildHabit() + assertThat(habitList.indexOf(h2), equalTo(-1)) + h2.id = 1000L + assertThat(habitList.indexOf(h2), equalTo(-1)) + } + + @Test + @Throws(Exception::class) + fun testRemove() { + val h = habitList.getById(2) + habitList.remove(h!!) + assertThat(habitList.indexOf(h), equalTo(-1)) + var rec = repository.find(2L) + assertNull(rec) + rec = repository.find(3L)!! + assertThat(rec.position, equalTo(1)) + } + + @Test + fun testReorder() { + val habit3 = habitList.getById(3)!! + val habit4 = habitList.getById(4)!! + habitList.reorder(habit4, habit3) + val record3 = repository.find(3L)!! + assertThat(record3.position, equalTo(3)) + val record4 = repository.find(4L)!! + assertThat(record4.position, equalTo(2)) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/EntryRecordTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/EntryRecordTest.kt similarity index 58% rename from uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/EntryRecordTest.java rename to uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/EntryRecordTest.kt index da651c3a4..2f561618b 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/EntryRecordTest.java +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/EntryRecordTest.kt @@ -16,24 +16,22 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.core.models.sqlite.records -package org.isoron.uhabits.core.models.sqlite.records; +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.models.Timestamp +import org.junit.Test -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsEqual.equalTo; - -public class EntryRecordTest extends BaseUnitTest -{ +class EntryRecordTest : BaseUnitTest() { @Test - public void testRecord() throws Exception - { - Entry check = new Entry(Timestamp.ZERO.plus(100), 50); - EntryRecord record = new EntryRecord(); - record.copyFrom(check); - assertThat(check, equalTo(record.toEntry())); + @Throws(Exception::class) + fun testRecord() { + val check = Entry(Timestamp.ZERO.plus(100), 50) + val record = EntryRecord() + record.copyFrom(check) + assertThat(check, equalTo(record.toEntry())) } } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecordTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecordTest.java deleted file mode 100644 index 37581ce40..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecordTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.models.sqlite.records; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.core.IsEqual.*; - -public class HabitRecordTest extends BaseUnitTest -{ - - @Test - public void testCopyRestore1() - { - Habit original = modelFactory.buildHabit(); - original.setName("Hello world"); - original.setQuestion("Did you greet the world today?"); - original.setColor(new PaletteColor(1)); - original.setArchived(true); - original.setFrequency(Frequency.THREE_TIMES_PER_WEEK); - original.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - original.setId(1000L); - original.setPosition(20); - - HabitRecord record = new HabitRecord(); - record.copyFrom(original); - - Habit duplicate = modelFactory.buildHabit(); - record.copyTo(duplicate); - - assertThat(original, equalTo(duplicate)); - } - - @Test - public void testCopyRestore2() - { - Habit original = modelFactory.buildHabit(); - original.setName("Hello world"); - original.setQuestion("Did you greet the world today?"); - original.setColor(new PaletteColor(5)); - original.setArchived(false); - original.setFrequency(Frequency.DAILY); - original.setReminder(null); - original.setId(1L); - original.setPosition(15); - original.setType(Habit.NUMBER_HABIT); - original.setTargetValue(100); - original.setTargetType(Habit.AT_LEAST); - original.setUnit("miles"); - - HabitRecord record = new HabitRecord(); - record.copyFrom(original); - - Habit duplicate = modelFactory.buildHabit(); - record.copyTo(duplicate); - - assertThat(original, equalTo(duplicate)); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecordTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecordTest.kt new file mode 100644 index 000000000..55953f0c8 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/models/sqlite/records/HabitRecordTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016-2021 Á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.core.models.sqlite.records + +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Frequency +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.PaletteColor +import org.isoron.uhabits.core.models.Reminder +import org.isoron.uhabits.core.models.WeekdayList +import org.junit.Test + +class HabitRecordTest : BaseUnitTest() { + @Test + fun testCopyRestore1() { + val original = modelFactory.buildHabit().apply() { + name = "Hello world" + question = "Did you greet the world today?" + color = PaletteColor(1) + isArchived = true + frequency = Frequency.THREE_TIMES_PER_WEEK + reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + id = 1000L + position = 20 + } + val record = HabitRecord() + record.copyFrom(original) + val duplicate = modelFactory.buildHabit() + record.copyTo(duplicate) + assertThat(original, equalTo(duplicate)) + } + + @Test + fun testCopyRestore2() { + val original = modelFactory.buildHabit().apply() { + name = "Hello world" + question = "Did you greet the world today?" + color = PaletteColor(5) + isArchived = false + frequency = Frequency.DAILY + reminder = null + id = 1L + position = 15 + type = Habit.NUMBER_HABIT + targetValue = 100.0 + targetType = Habit.AT_LEAST + unit = "miles" + } + val record = HabitRecord() + record.copyFrom(original) + val duplicate = modelFactory.buildHabit() + record.copyTo(duplicate) + assertThat(original, equalTo(duplicate)) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PreferencesTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PreferencesTest.java deleted file mode 100644 index 3bab7abe5..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PreferencesTest.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.preferences; - -import androidx.annotation.*; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.ui.*; -import org.junit.*; -import org.mockito.*; - -import java.io.*; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.Assert.*; - -public class PreferencesTest extends BaseUnitTest -{ - @NonNull - private Preferences prefs; - - @Mock - private Preferences.Listener listener; - - private PropertiesStorage storage; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - File file = File.createTempFile("prefs", ".properties"); - file.deleteOnExit(); - storage = new PropertiesStorage(file); - prefs = new Preferences(storage); - prefs.addListener(listener); - } - - @Test - public void testClear() throws Exception - { - prefs.setDefaultHabitColor(99); - prefs.clear(); - assertThat(prefs.getDefaultHabitColor(0), equalTo(0)); - } - - @Test - public void testHabitColor() throws Exception - { - assertThat(prefs.getDefaultHabitColor(999), equalTo(999)); - prefs.setDefaultHabitColor(10); - assertThat(prefs.getDefaultHabitColor(999), equalTo(10)); - } - - @Test - public void testDefaultOrder() throws Exception - { - assertThat(prefs.getDefaultPrimaryOrder(), equalTo(HabitList.Order.BY_POSITION)); - - prefs.setDefaultPrimaryOrder(HabitList.Order.BY_SCORE_DESC); - assertThat(prefs.getDefaultPrimaryOrder(), equalTo(HabitList.Order.BY_SCORE_DESC)); - - storage.putString("pref_default_order", "BOGUS"); - assertThat(prefs.getDefaultPrimaryOrder(), equalTo(HabitList.Order.BY_POSITION)); - assertThat(storage.getString("pref_default_order", ""), equalTo("BY_POSITION")); - } - - @Test - public void testScoreCardSpinnerPosition() throws Exception - { - assertThat(prefs.getScoreCardSpinnerPosition(), equalTo(1)); - - prefs.setScoreCardSpinnerPosition(4); - assertThat(prefs.getScoreCardSpinnerPosition(), equalTo(4)); - - storage.putInt("pref_score_view_interval", 9000); - assertThat(prefs.getScoreCardSpinnerPosition(), equalTo(4)); - } - - @Test - public void testLastHint() throws Exception - { - assertThat(prefs.getLastHintNumber(), equalTo(-1)); - assertNull(prefs.getLastHintTimestamp()); - - prefs.updateLastHint(34, Timestamp.ZERO.plus(100)); - assertThat(prefs.getLastHintNumber(), equalTo(34)); - assertThat(prefs.getLastHintTimestamp(), equalTo(Timestamp.ZERO.plus(100))); - } - - @Test - public void testTheme() throws Exception - { - assertThat(prefs.getTheme(), equalTo(ThemeSwitcher.THEME_AUTOMATIC)); - prefs.setTheme(ThemeSwitcher.THEME_DARK); - assertThat(prefs.getTheme(), equalTo(ThemeSwitcher.THEME_DARK)); - - assertFalse(prefs.isPureBlackEnabled()); - prefs.setPureBlackEnabled(true); - assertTrue(prefs.isPureBlackEnabled()); - } - - @Test - public void testNotifications() throws Exception - { - assertFalse(prefs.shouldMakeNotificationsSticky()); - prefs.setNotificationsSticky(true); - assertTrue(prefs.shouldMakeNotificationsSticky()); - - assertFalse(prefs.shouldMakeNotificationsLed()); - prefs.setNotificationsLed(true); - assertTrue(prefs.shouldMakeNotificationsLed()); - - assertThat(prefs.getSnoozeInterval(), equalTo(15L)); - prefs.setSnoozeInterval(30); - assertThat(prefs.getSnoozeInterval(), equalTo(30L)); - } - - @Test - public void testAppVersionAndLaunch() throws Exception - { - assertThat(prefs.getLastAppVersion(), equalTo(0)); - prefs.setLastAppVersion(23); - assertThat(prefs.getLastAppVersion(), equalTo(23)); - - assertTrue(prefs.isFirstRun()); - prefs.setFirstRun(false); - assertFalse(prefs.isFirstRun()); - - assertThat(prefs.getLaunchCount(), equalTo(0)); - prefs.incrementLaunchCount(); - assertThat(prefs.getLaunchCount(), equalTo(1)); - } - - @Test - public void testCheckmarks() throws Exception - { - assertFalse(prefs.isCheckmarkSequenceReversed()); - prefs.setCheckmarkSequenceReversed(true); - assertTrue(prefs.isCheckmarkSequenceReversed()); - - assertFalse(prefs.isShortToggleEnabled()); - prefs.setShortToggleEnabled(true); - assertTrue(prefs.isShortToggleEnabled()); - } - - @Test - public void testDeveloper() throws Exception - { - assertFalse(prefs.isDeveloper()); - prefs.setDeveloper(true); - assertTrue(prefs.isDeveloper()); - } - - @Test - public void testFiltering() throws Exception - { - assertFalse(prefs.getShowArchived()); - assertTrue(prefs.getShowCompleted()); - - prefs.setShowArchived(true); - prefs.setShowCompleted(false); - - assertTrue(prefs.getShowArchived()); - assertFalse(prefs.getShowCompleted()); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PreferencesTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PreferencesTest.kt new file mode 100644 index 000000000..340f97440 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PreferencesTest.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2016-2021 Á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.core.preferences + +import com.nhaarman.mockitokotlin2.mock +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertNull +import junit.framework.Assert.assertTrue +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.Timestamp.ZERO +import org.isoron.uhabits.core.ui.ThemeSwitcher +import org.junit.Before +import org.junit.Test +import java.io.File + +class PreferencesTest : BaseUnitTest() { + private lateinit var prefs: Preferences + + private var listener: Preferences.Listener = mock() + private lateinit var storage: PropertiesStorage + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + val file = File.createTempFile("prefs", ".properties") + file.deleteOnExit() + storage = PropertiesStorage(file) + prefs = Preferences(storage) + prefs.addListener(listener) + } + + @Test + @Throws(Exception::class) + fun testClear() { + prefs.setDefaultHabitColor(99) + prefs.clear() + assertThat(prefs.getDefaultHabitColor(0), equalTo(0)) + } + + @Test + @Throws(Exception::class) + fun testHabitColor() { + assertThat(prefs.getDefaultHabitColor(999), equalTo(999)) + prefs.setDefaultHabitColor(10) + assertThat(prefs.getDefaultHabitColor(999), equalTo(10)) + } + + @Test + @Throws(Exception::class) + fun testDefaultOrder() { + assertThat(prefs.defaultPrimaryOrder, equalTo(HabitList.Order.BY_POSITION)) + prefs.defaultPrimaryOrder = HabitList.Order.BY_SCORE_DESC + assertThat(prefs.defaultPrimaryOrder, equalTo(HabitList.Order.BY_SCORE_DESC)) + storage.putString("pref_default_order", "BOGUS") + assertThat(prefs.defaultPrimaryOrder, equalTo(HabitList.Order.BY_POSITION)) + assertThat( + storage.getString("pref_default_order", ""), + equalTo("BY_POSITION") + ) + } + + @Test + @Throws(Exception::class) + fun testScoreCardSpinnerPosition() { + assertThat(prefs.scoreCardSpinnerPosition, equalTo(1)) + prefs.scoreCardSpinnerPosition = 4 + assertThat(prefs.scoreCardSpinnerPosition, equalTo(4)) + storage.putInt("pref_score_view_interval", 9000) + assertThat(prefs.scoreCardSpinnerPosition, equalTo(4)) + } + + @Test + @Throws(Exception::class) + fun testLastHint() { + assertThat(prefs.lastHintNumber, equalTo(-1)) + assertNull(prefs.lastHintTimestamp) + prefs.updateLastHint(34, ZERO.plus(100)) + assertThat(prefs.lastHintNumber, equalTo(34)) + assertThat(prefs.lastHintTimestamp, equalTo(ZERO.plus(100))) + } + + @Test + @Throws(Exception::class) + fun testTheme() { + assertThat(prefs.theme, equalTo(ThemeSwitcher.THEME_AUTOMATIC)) + prefs.theme = ThemeSwitcher.THEME_DARK + assertThat(prefs.theme, equalTo(ThemeSwitcher.THEME_DARK)) + assertFalse(prefs.isPureBlackEnabled) + prefs.isPureBlackEnabled = true + assertTrue(prefs.isPureBlackEnabled) + } + + @Test + @Throws(Exception::class) + fun testNotifications() { + assertFalse(prefs.shouldMakeNotificationsSticky()) + prefs.setNotificationsSticky(true) + assertTrue(prefs.shouldMakeNotificationsSticky()) + assertFalse(prefs.shouldMakeNotificationsLed()) + prefs.setNotificationsLed(true) + assertTrue(prefs.shouldMakeNotificationsLed()) + assertThat(prefs.snoozeInterval, equalTo(15L)) + prefs.setSnoozeInterval(30) + assertThat(prefs.snoozeInterval, equalTo(30L)) + } + + @Test + @Throws(Exception::class) + fun testAppVersionAndLaunch() { + assertThat(prefs.lastAppVersion, equalTo(0)) + prefs.lastAppVersion = 23 + assertThat(prefs.lastAppVersion, equalTo(23)) + assertTrue(prefs.isFirstRun) + prefs.isFirstRun = false + assertFalse(prefs.isFirstRun) + assertThat(prefs.launchCount, equalTo(0)) + prefs.incrementLaunchCount() + assertThat(prefs.launchCount, equalTo(1)) + } + + @Test + @Throws(Exception::class) + fun testCheckmarks() { + assertFalse(prefs.isCheckmarkSequenceReversed) + prefs.isCheckmarkSequenceReversed = true + assertTrue(prefs.isCheckmarkSequenceReversed) + assertFalse(prefs.isShortToggleEnabled) + prefs.isShortToggleEnabled = true + assertTrue(prefs.isShortToggleEnabled) + } + + @Test + @Throws(Exception::class) + fun testDeveloper() { + assertFalse(prefs.isDeveloper) + prefs.isDeveloper = true + assertTrue(prefs.isDeveloper) + } + + @Test + @Throws(Exception::class) + fun testFiltering() { + assertFalse(prefs.showArchived) + assertTrue(prefs.showCompleted) + prefs.showArchived = true + prefs.showCompleted = false + assertTrue(prefs.showArchived) + assertFalse(prefs.showCompleted) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PropertiesStorageTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PropertiesStorageTest.java deleted file mode 100644 index 9df289bba..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PropertiesStorageTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.preferences; - -import org.isoron.uhabits.core.*; -import org.junit.*; - -import java.io.*; -import java.util.*; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertFalse; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.Assert.assertTrue; - -public class PropertiesStorageTest extends BaseUnitTest -{ - private PropertiesStorage storage; - - private File file; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - - file = File.createTempFile("test", ".properties"); - file.deleteOnExit(); - - storage = new PropertiesStorage(file); - } - - @Test - public void testPutGetRemove() throws Exception - { - storage.putBoolean("booleanKey", true); - assertTrue(storage.getBoolean("booleanKey", false)); - assertFalse(storage.getBoolean("random", false)); - - storage.putInt("intKey", 64); - assertThat(storage.getInt("intKey", 200), equalTo(64)); - assertThat(storage.getInt("random", 200), equalTo(200)); - - storage.putLong("longKey", 64L); - assertThat(storage.getLong("intKey", 200L), equalTo(64L)); - assertThat(storage.getLong("random", 200L), equalTo(200L)); - - storage.putString("stringKey", "Hello"); - assertThat(storage.getString("stringKey", ""), equalTo("Hello")); - assertThat(storage.getString("random", ""), equalTo("")); - - storage.remove("stringKey"); - assertThat(storage.getString("stringKey", ""), equalTo("")); - - storage.clear(); - assertThat(storage.getLong("intKey", 200L), equalTo(200L)); - assertFalse(storage.getBoolean("booleanKey", false)); - } - - @Test - public void testPersistence() throws Exception - { - storage.putBoolean("booleanKey", true); - storage.putInt("intKey", 64); - storage.putLong("longKey", 64L); - storage.putString("stringKey", "Hello"); - - PropertiesStorage storage2 = new PropertiesStorage(file); - assertTrue(storage2.getBoolean("booleanKey", false)); - assertThat(storage2.getInt("intKey", 200), equalTo(64)); - assertThat(storage2.getLong("intKey", 200L), equalTo(64L)); - assertThat(storage2.getString("stringKey", ""), equalTo("Hello")); - } - - @Test - public void testLongArray() throws Exception - { - long[] expected1 = new long[]{1L, 2L, 3L, 5L}; - long[] expected2 = new long[]{1L}; - long[] expected3 = new long[]{}; - long[] expected4 = new long[]{}; - - storage.putLongArray("key1", expected1); - storage.putLongArray("key2", expected2); - storage.putLongArray("key3", expected3); - - long[] actual1 = storage.getLongArray("key1", new long[]{}); - long[] actual2 = storage.getLongArray("key2", new long[]{}); - long[] actual3 = storage.getLongArray("key3", new long[]{}); - long[] actual4 = storage.getLongArray("invalidKey", new long[]{}); - - assertTrue(Arrays.equals(actual1, expected1)); - assertTrue(Arrays.equals(actual2, expected2)); - assertTrue(Arrays.equals(actual3, expected3)); - assertTrue(Arrays.equals(actual4, expected4)); - - assertEquals("1,2,3,5", storage.getString("key1", "")); - assertEquals(1, storage.getLong("key2", -1)); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PropertiesStorageTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PropertiesStorageTest.kt new file mode 100644 index 000000000..65066b916 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/preferences/PropertiesStorageTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016-2021 Á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.core.preferences + +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.junit.Before +import org.junit.Test +import java.io.File +import java.util.Arrays + +class PropertiesStorageTest : BaseUnitTest() { + private lateinit var storage: PropertiesStorage + private lateinit var file: File + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + file = File.createTempFile("test", ".properties") + file.deleteOnExit() + storage = PropertiesStorage(file) + } + + @Test + @Throws(Exception::class) + fun testPutGetRemove() { + storage.putBoolean("booleanKey", true) + assertTrue(storage.getBoolean("booleanKey", false)) + assertFalse(storage.getBoolean("random", false)) + storage.putInt("intKey", 64) + assertThat(storage.getInt("intKey", 200), equalTo(64)) + assertThat(storage.getInt("random", 200), equalTo(200)) + storage.putLong("longKey", 64L) + assertThat(storage.getLong("intKey", 200L), equalTo(64L)) + assertThat(storage.getLong("random", 200L), equalTo(200L)) + storage.putString("stringKey", "Hello") + assertThat(storage.getString("stringKey", ""), equalTo("Hello")) + assertThat(storage.getString("random", ""), equalTo("")) + storage.remove("stringKey") + assertThat(storage.getString("stringKey", ""), equalTo("")) + storage.clear() + assertThat(storage.getLong("intKey", 200L), equalTo(200L)) + assertFalse(storage.getBoolean("booleanKey", false)) + } + + @Test + @Throws(Exception::class) + fun testPersistence() { + storage.putBoolean("booleanKey", true) + storage.putInt("intKey", 64) + storage.putLong("longKey", 64L) + storage.putString("stringKey", "Hello") + val storage2 = PropertiesStorage(file) + assertTrue(storage2.getBoolean("booleanKey", false)) + assertThat(storage2.getInt("intKey", 200), equalTo(64)) + assertThat(storage2.getLong("intKey", 200L), equalTo(64L)) + assertThat(storage2.getString("stringKey", ""), equalTo("Hello")) + } + + @Test + @Throws(Exception::class) + fun testLongArray() { + val expected1 = longArrayOf(1L, 2L, 3L, 5L) + val expected2 = longArrayOf(1L) + val expected3 = longArrayOf() + val expected4 = longArrayOf() + storage.putLongArray("key1", expected1) + storage.putLongArray("key2", expected2) + storage.putLongArray("key3", expected3) + val actual1 = storage.getLongArray("key1", longArrayOf()) + val actual2 = storage.getLongArray("key2", longArrayOf()) + val actual3 = storage.getLongArray("key3", longArrayOf()) + val actual4 = storage.getLongArray("invalidKey", longArrayOf()) + assertTrue(Arrays.equals(actual1, expected1)) + assertTrue(Arrays.equals(actual2, expected2)) + assertTrue(Arrays.equals(actual3, expected3)) + assertTrue(Arrays.equals(actual4, expected4)) + assertEquals("1,2,3,5", storage.getString("key1", "")) + assertEquals(1, storage.getLong("key2", -1)) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java deleted file mode 100644 index 08ae9010c..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.reminders; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; -import org.junit.runner.*; -import org.mockito.*; -import org.mockito.junit.*; - -import java.util.*; - -import static org.isoron.uhabits.core.utils.DateUtils.applyTimezone; -import static org.isoron.uhabits.core.utils.DateUtils.removeTimezone; -import static org.isoron.uhabits.core.utils.DateUtils.setFixedLocalTime; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class ReminderSchedulerTest extends BaseUnitTest -{ - private final long habitId = 10L; - - private Habit habit; - - private ReminderScheduler reminderScheduler; - - @Mock - private ReminderScheduler.SystemScheduler sys; - - @Mock - private WidgetPreferences widgetPreferences; - - @Before - @Override - public void setUp() throws Exception - { - super.setUp(); - habit = fixtures.createEmptyHabit(); - habit.setId(habitId); - - reminderScheduler = - new ReminderScheduler(commandRunner, habitList, sys, widgetPreferences); - - DateUtils.setFixedTimeZone(TimeZone.getTimeZone("GMT-4")); - } - - @Test - public void testScheduleAll() - { - long now = unixTime(2015, 1, 26, 13, 0); - setFixedLocalTime(now); - - Habit h1 = fixtures.createEmptyHabit(); - Habit h2 = fixtures.createEmptyHabit(); - Habit h3 = fixtures.createEmptyHabit(); - h1.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - h2.setReminder(new Reminder(18, 30, WeekdayList.EVERY_DAY)); - h3.setReminder(null); - habitList.add(h1); - habitList.add(h2); - habitList.add(h3); - - reminderScheduler.scheduleAll(); - - verify(sys).scheduleShowReminder(eq(unixTime(2015, 1, 27, 12, 30)), - eq(h1), anyLong()); - verify(sys).scheduleShowReminder(eq(unixTime(2015, 1, 26, 22, 30)), - eq(h2), anyLong()); - } - - @Test - public void testSchedule_atSpecificTime() - { - long atTime = unixTime(2015, 1, 30, 11, 30); - long expectedCheckmarkTime = unixTime(2015, 1, 30, 0, 0); - - habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - scheduleAndVerify(atTime, expectedCheckmarkTime, atTime); - } - - @Test - public void testSchedule_withSnooze() - { - long now = removeTimezone(unixTime(2015, 1, 1, 15, 0)); - setFixedLocalTime(now); - - long snoozeTimeInFuture = unixTime(2015, 1, 1, 21, 0); - long snoozeTimeInPast = unixTime(2015, 1, 1, 7, 0); - long regularReminderTime = applyTimezone(unixTime(2015, 1, 2, 8, 30)); - long todayCheckmarkTime = unixTime(2015, 1, 1, 0, 0); - long tomorrowCheckmarkTime = unixTime(2015, 1, 2, 0, 0); - habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - - when(widgetPreferences.getSnoozeTime(habitId)).thenReturn(snoozeTimeInFuture); - reminderScheduler.schedule(habit); - verify(sys).scheduleShowReminder(snoozeTimeInFuture, habit, todayCheckmarkTime); - - when(widgetPreferences.getSnoozeTime(habitId)).thenReturn(snoozeTimeInPast); - reminderScheduler.schedule(habit); - verify(sys).scheduleShowReminder(regularReminderTime, habit, tomorrowCheckmarkTime); - } - - @Test - public void testSchedule_laterToday() - { - long now = unixTime(2015, 1, 26, 6, 30); - setFixedLocalTime(now); - - long expectedCheckmarkTime = unixTime(2015, 1, 26, 0, 0); - long expectedReminderTime = unixTime(2015, 1, 26, 12, 30); - - habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - scheduleAndVerify(null, expectedCheckmarkTime, expectedReminderTime); - } - - @Test - public void testSchedule_tomorrow() - { - long now = unixTime(2015, 1, 26, 13, 0); - setFixedLocalTime(now); - - long expectedCheckmarkTime = unixTime(2015, 1, 27, 0, 0); - long expectedReminderTime = unixTime(2015, 1, 27, 12, 30); - - habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); - scheduleAndVerify(null, expectedCheckmarkTime, expectedReminderTime); - } - - @Test - public void testSchedule_withoutReminder() - { - reminderScheduler.schedule(habit); - } - - public long unixTime(int year, int month, int day, int hour, int minute) - { - Calendar cal = DateUtils.getStartOfTodayCalendar(); - cal.set(year, month, day, hour, minute); - return cal.getTimeInMillis(); - } - - private void scheduleAndVerify(Long atTime, - long expectedCheckmarkTime, - long expectedReminderTime) - { - if(atTime == null) reminderScheduler.schedule(habit); - else reminderScheduler.scheduleAtTime(habit, atTime); - verify(sys).scheduleShowReminder(expectedReminderTime, habit, - expectedCheckmarkTime); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt new file mode 100644 index 000000000..d354e81f0 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/reminders/ReminderSchedulerTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016-2021 Á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.core.reminders + +import com.nhaarman.mockitokotlin2.eq +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.Reminder +import org.isoron.uhabits.core.models.WeekdayList +import org.isoron.uhabits.core.preferences.WidgetPreferences +import org.isoron.uhabits.core.utils.DateUtils.Companion.applyTimezone +import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar +import org.isoron.uhabits.core.utils.DateUtils.Companion.removeTimezone +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedLocalTime +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedTimeZone +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.junit.MockitoJUnitRunner +import java.util.Calendar +import java.util.TimeZone + +@RunWith(MockitoJUnitRunner::class) +class ReminderSchedulerTest : BaseUnitTest() { + private val habitId = 10L + private lateinit var habit: Habit + private lateinit var reminderScheduler: ReminderScheduler + + private val sys: ReminderScheduler.SystemScheduler = mock() + private val widgetPreferences: WidgetPreferences = mock() + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createEmptyHabit() + habit.id = habitId + reminderScheduler = + ReminderScheduler(commandRunner, habitList, sys, widgetPreferences) + setFixedTimeZone(TimeZone.getTimeZone("GMT-4")) + } + + @Test + fun testScheduleAll() { + val now = unixTime(2015, 1, 26, 13, 0) + setFixedLocalTime(now) + val h1 = fixtures.createEmptyHabit() + val h2 = fixtures.createEmptyHabit() + val h3 = fixtures.createEmptyHabit() + h1.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + h2.reminder = Reminder(18, 30, WeekdayList.EVERY_DAY) + h3.reminder = null + habitList.add(h1) + habitList.add(h2) + habitList.add(h3) + reminderScheduler.scheduleAll() + verify(sys).scheduleShowReminder( + eq(unixTime(2015, 1, 27, 12, 30)), + eq(h1), + anyLong() + ) + verify(sys).scheduleShowReminder( + eq(unixTime(2015, 1, 26, 22, 30)), + eq(h2), + anyLong() + ) + } + + @Test + fun testSchedule_atSpecificTime() { + val atTime = unixTime(2015, 1, 30, 11, 30) + val expectedCheckmarkTime = unixTime(2015, 1, 30, 0, 0) + habit.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + scheduleAndVerify(atTime, expectedCheckmarkTime, atTime) + } + + @Test + fun testSchedule_withSnooze() { + val now = removeTimezone(unixTime(2015, 1, 1, 15, 0)) + setFixedLocalTime(now) + val snoozeTimeInFuture = unixTime(2015, 1, 1, 21, 0) + val snoozeTimeInPast = unixTime(2015, 1, 1, 7, 0) + val regularReminderTime = applyTimezone(unixTime(2015, 1, 2, 8, 30)) + val todayCheckmarkTime = unixTime(2015, 1, 1, 0, 0) + val tomorrowCheckmarkTime = unixTime(2015, 1, 2, 0, 0) + habit.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + whenever(widgetPreferences.getSnoozeTime(habitId)).thenReturn(snoozeTimeInFuture) + reminderScheduler.schedule(habit) + verify(sys).scheduleShowReminder(snoozeTimeInFuture, habit, todayCheckmarkTime) + whenever(widgetPreferences.getSnoozeTime(habitId)).thenReturn(snoozeTimeInPast) + reminderScheduler.schedule(habit) + verify(sys) + .scheduleShowReminder(regularReminderTime, habit, tomorrowCheckmarkTime) + } + + @Test + fun testSchedule_laterToday() { + val now = unixTime(2015, 1, 26, 6, 30) + setFixedLocalTime(now) + val expectedCheckmarkTime = unixTime(2015, 1, 26, 0, 0) + val expectedReminderTime = unixTime(2015, 1, 26, 12, 30) + habit.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + scheduleAndVerify(null, expectedCheckmarkTime, expectedReminderTime) + } + + @Test + fun testSchedule_tomorrow() { + val now = unixTime(2015, 1, 26, 13, 0) + setFixedLocalTime(now) + val expectedCheckmarkTime = unixTime(2015, 1, 27, 0, 0) + val expectedReminderTime = unixTime(2015, 1, 27, 12, 30) + habit.reminder = Reminder(8, 30, WeekdayList.EVERY_DAY) + scheduleAndVerify(null, expectedCheckmarkTime, expectedReminderTime) + } + + @Test + fun testSchedule_withoutReminder() { + reminderScheduler.schedule(habit) + } + + override fun unixTime(year: Int, month: Int, day: Int, hour: Int, minute: Int): Long { + val cal: Calendar = getStartOfTodayCalendar() + cal[year, month, day, hour] = minute + return cal.timeInMillis + } + + private fun scheduleAndVerify( + atTime: Long?, + expectedCheckmarkTime: Long, + expectedReminderTime: Long + ) { + if (atTime == null) reminderScheduler.schedule(habit) else reminderScheduler.scheduleAtTime( + habit, + atTime + ) + verify(sys).scheduleShowReminder( + expectedReminderTime, + habit, + expectedCheckmarkTime + ) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.java deleted file mode 100644 index 9a3266776..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.tasks; - -import org.isoron.uhabits.core.*; -import org.junit.*; -import org.junit.runner.*; -import org.junit.runners.*; -import org.mockito.*; - -import static org.mockito.Mockito.*; - -@RunWith(JUnit4.class) -public class SingleThreadTaskRunnerTest extends BaseUnitTest -{ - private SingleThreadTaskRunner runner; - - private Task task; - - @Override - public void setUp() throws Exception - { - super.setUp(); - runner = new SingleThreadTaskRunner(); - task = mock(Task.class); - } - - @Test - public void test() - { - runner.execute(task); - - InOrder inOrder = inOrder(task); - inOrder.verify(task).onAttached(runner); - inOrder.verify(task).onPreExecute(); - inOrder.verify(task).doInBackground(); - inOrder.verify(task).onPostExecute(); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.kt new file mode 100644 index 000000000..0dbc97bfd --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/tasks/SingleThreadTaskRunnerTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016-2021 Á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.core.tasks + +import com.nhaarman.mockitokotlin2.inOrder +import com.nhaarman.mockitokotlin2.mock +import org.isoron.uhabits.core.BaseUnitTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class SingleThreadTaskRunnerTest : BaseUnitTest() { + private lateinit var runner: SingleThreadTaskRunner + private var task: Task = mock() + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + runner = SingleThreadTaskRunner() + } + + @Test + fun test() { + runner.execute(task) + val inOrder = inOrder(task) + inOrder.verify(task).onAttached(runner) + inOrder.verify(task).onPreExecute() + inOrder.verify(task).doInBackground() + inOrder.verify(task).onPostExecute() + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java deleted file mode 100644 index e4d629f1f..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list; - -import org.apache.commons.lang3.*; -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import java.util.*; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.core.IsEqual.*; -import static org.mockito.Mockito.*; - -public class HabitCardListCacheTest extends BaseUnitTest -{ - private HabitCardListCache cache; - - private HabitCardListCache.Listener listener; - - Timestamp today = DateUtils.getToday(); - - @Override - public void setUp() throws Exception - { - super.setUp(); - habitList.removeAll(); - - for (int i = 0; i < 10; i++) - { - if (i == 3) habitList.add(fixtures.createLongHabit()); - else habitList.add(fixtures.createShortHabit()); - } - - cache = new HabitCardListCache(habitList, commandRunner, taskRunner); - cache.setCheckmarkCount(10); - cache.refreshAllHabits(); - cache.onAttached(); - - listener = mock(HabitCardListCache.Listener.class); - cache.setListener(listener); - } - - @Override - public void tearDown() - { - cache.onDetached(); - } - - @Test - public void testCommandListener_all() - { - assertThat(cache.getHabitCount(), equalTo(10)); - - Habit h = habitList.getByPosition(0); - commandRunner.run( - new DeleteHabitsCommand(habitList, Collections.singletonList(h)) - ); - - verify(listener).onItemRemoved(0); - verify(listener).onRefreshFinished(); - assertThat(cache.getHabitCount(), equalTo(9)); - } - - @Test - public void testCommandListener_single() - { - Habit h2 = habitList.getByPosition(2); - commandRunner.run(new CreateRepetitionCommand(habitList, h2, today, Entry.NO)); - verify(listener).onItemChanged(2); - verify(listener).onRefreshFinished(); - verifyNoMoreInteractions(listener); - } - - @Test - public void testGet() - { - assertThat(cache.getHabitCount(), equalTo(10)); - - Habit h = habitList.getByPosition(3); - Assert.assertNotNull(h.getId()); - double score = h.getScores().get(today).getValue(); - - assertThat(cache.getHabitByPosition(3), equalTo(h)); - assertThat(cache.getScore(h.getId()), equalTo(score)); - - int[] actualCheckmarks = cache.getCheckmarks(h.getId()); - int[] expectedCheckmarks = ArrayUtils.toPrimitive(h.getComputedEntries() - .getByInterval(today.minus(9), today) - .stream() - .map(Entry::getValue) - .toArray(Integer[]::new)); - - assertThat(actualCheckmarks, equalTo(expectedCheckmarks)); - } - - @Test - public void testRemoval() - { - removeHabitAt(0); - removeHabitAt(3); - - cache.refreshAllHabits(); - verify(listener).onItemRemoved(0); - verify(listener).onItemRemoved(3); - verify(listener).onRefreshFinished(); - assertThat(cache.getHabitCount(), equalTo(8)); - } - - @Test - public void testRefreshWithNoChanges() - { - cache.refreshAllHabits(); - verify(listener).onRefreshFinished(); - verifyNoMoreInteractions(listener); - } - - @Test - public void testReorder_onCache() - { - Habit h2 = cache.getHabitByPosition(2); - Habit h3 = cache.getHabitByPosition(3); - Habit h7 = cache.getHabitByPosition(7); - - cache.reorder(2, 7); - - assertThat(cache.getHabitByPosition(2), equalTo(h3)); - assertThat(cache.getHabitByPosition(7), equalTo(h2)); - assertThat(cache.getHabitByPosition(6), equalTo(h7)); - verify(listener).onItemMoved(2, 7); - verifyNoMoreInteractions(listener); - } - - @Test - public void testReorder_onList() - { - Habit h2 = habitList.getByPosition(2); - Habit h3 = habitList.getByPosition(3); - Habit h7 = habitList.getByPosition(7); - - assertThat(cache.getHabitByPosition(2), equalTo(h2)); - assertThat(cache.getHabitByPosition(7), equalTo(h7)); - reset(listener); - - habitList.reorder(h2, h7); - cache.refreshAllHabits(); - - assertThat(cache.getHabitByPosition(2), equalTo(h3)); - assertThat(cache.getHabitByPosition(7), equalTo(h2)); - assertThat(cache.getHabitByPosition(6), equalTo(h7)); - - verify(listener).onItemMoved(3, 2); - verify(listener).onItemMoved(4, 3); - verify(listener).onItemMoved(5, 4); - verify(listener).onItemMoved(6, 5); - verify(listener).onItemMoved(7, 6); - verify(listener).onRefreshFinished(); - verifyNoMoreInteractions(listener); - } - - protected void removeHabitAt(int position) - { - Habit h = habitList.getByPosition(position); - Assert.assertNotNull(h); - habitList.remove(h); - } - -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.kt new file mode 100644 index 000000000..cf4c284d8 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HabitCardListCacheTest.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.reset +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.commands.CreateRepetitionCommand +import org.isoron.uhabits.core.commands.DeleteHabitsCommand +import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.junit.Test + +class HabitCardListCacheTest : BaseUnitTest() { + private lateinit var cache: HabitCardListCache + private lateinit var listener: HabitCardListCache.Listener + var today = getToday() + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habitList.removeAll() + for (i in 0..9) { + if (i == 3) habitList.add(fixtures.createLongHabit()) else habitList.add(fixtures.createShortHabit()) + } + cache = HabitCardListCache(habitList, commandRunner, taskRunner) + cache.setCheckmarkCount(10) + cache.refreshAllHabits() + cache.onAttached() + listener = mock() + cache.setListener(listener) + } + + override fun tearDown() { + cache.onDetached() + } + + @Test + fun testCommandListener_all() { + assertThat(cache.habitCount, equalTo(10)) + val h = habitList.getByPosition(0) + commandRunner.run( + DeleteHabitsCommand(habitList, listOf(h)) + ) + verify(listener).onItemRemoved(0) + verify(listener).onRefreshFinished() + assertThat(cache.habitCount, equalTo(9)) + } + + @Test + fun testCommandListener_single() { + val h2 = habitList.getByPosition(2) + commandRunner.run(CreateRepetitionCommand(habitList, h2, today, Entry.NO)) + verify(listener).onItemChanged(2) + verify(listener).onRefreshFinished() + verifyNoMoreInteractions(listener) + } + + @Test + fun testGet() { + assertThat(cache.habitCount, equalTo(10)) + val h = habitList.getByPosition(3) + val score = h.scores[today].value + assertThat(cache.getHabitByPosition(3), equalTo(h)) + assertThat(cache.getScore(h.id!!), equalTo(score)) + val actualCheckmarks = cache.getCheckmarks(h.id!!) + + val expectedCheckmarks = h + .computedEntries + .getByInterval(today.minus(9), today) + .map { it.value }.toIntArray() + assertThat(actualCheckmarks, equalTo(expectedCheckmarks)) + } + + @Test + fun testRemoval() { + removeHabitAt(0) + removeHabitAt(3) + cache.refreshAllHabits() + verify(listener).onItemRemoved(0) + verify(listener).onItemRemoved(3) + verify(listener).onRefreshFinished() + assertThat(cache.habitCount, equalTo(8)) + } + + @Test + fun testRefreshWithNoChanges() { + cache.refreshAllHabits() + verify(listener).onRefreshFinished() + verifyNoMoreInteractions(listener) + } + + @Test + fun testReorder_onCache() { + val h2 = cache.getHabitByPosition(2) + val h3 = cache.getHabitByPosition(3) + val h7 = cache.getHabitByPosition(7) + cache.reorder(2, 7) + assertThat(cache.getHabitByPosition(2), equalTo(h3)) + assertThat(cache.getHabitByPosition(7), equalTo(h2)) + assertThat(cache.getHabitByPosition(6), equalTo(h7)) + verify(listener).onItemMoved(2, 7) + verifyNoMoreInteractions(listener) + } + + @Test + fun testReorder_onList() { + val h2 = habitList.getByPosition(2) + val h3 = habitList.getByPosition(3) + val h7 = habitList.getByPosition(7) + assertThat(cache.getHabitByPosition(2), equalTo(h2)) + assertThat(cache.getHabitByPosition(7), equalTo(h7)) + reset(listener) + habitList.reorder(h2, h7) + cache.refreshAllHabits() + assertThat(cache.getHabitByPosition(2), equalTo(h3)) + assertThat(cache.getHabitByPosition(7), equalTo(h2)) + assertThat(cache.getHabitByPosition(6), equalTo(h7)) + verify(listener).onItemMoved(3, 2) + verify(listener).onItemMoved(4, 3) + verify(listener).onItemMoved(5, 4) + verify(listener).onItemMoved(6, 5) + verify(listener).onItemMoved(7, 6) + verify(listener).onRefreshFinished() + verifyNoMoreInteractions(listener) + } + + private fun removeHabitAt(position: Int) { + val h = habitList.getByPosition(position) + habitList.remove(h) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HintListTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HintListTest.java deleted file mode 100644 index 7d8e06550..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HintListTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; -import org.mockito.*; - -import static junit.framework.TestCase.assertFalse; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.*; - -public class HintListTest extends BaseUnitTest -{ - private HintList hintList; - - private String[] hints; - - @Mock - private Preferences prefs; - - private Timestamp today; - - private Timestamp yesterday; - - @Override - public void setUp() throws Exception - { - super.setUp(); - today = DateUtils.getToday(); - yesterday = today.minus(1); - - hints = new String[]{ "hint1", "hint2", "hint3" }; - hintList = new HintList(prefs, hints); - } - - @Test - public void pop() throws Exception - { - when(prefs.getLastHintNumber()).thenReturn(-1); - assertThat(hintList.pop(), equalTo("hint1")); - verify(prefs).updateLastHint(0, today); - - when(prefs.getLastHintNumber()).thenReturn(2); - assertNull(hintList.pop()); - } - - @Test - public void shouldShow() throws Exception - { - when(prefs.getLastHintTimestamp()).thenReturn(today); - assertFalse(hintList.shouldShow()); - - when(prefs.getLastHintTimestamp()).thenReturn(yesterday); - assertTrue(hintList.shouldShow()); - } -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HintListTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HintListTest.kt new file mode 100644 index 000000000..333236614 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/HintListTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertNull +import junit.framework.Assert.assertTrue +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.junit.Test +import org.mockito.Mock + +class HintListTest : BaseUnitTest() { + private lateinit var hintList: HintList + private lateinit var hints: Array + + @Mock + private val prefs: Preferences = mock() + private lateinit var today: Timestamp + private lateinit var yesterday: Timestamp + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + today = getToday() + yesterday = today.minus(1) + hints = arrayOf("hint1", "hint2", "hint3") + hintList = HintList(prefs, hints) + } + + @Test + @Throws(Exception::class) + fun pop() { + whenever(prefs.lastHintNumber).thenReturn(-1) + assertThat(hintList.pop(), equalTo("hint1")) + verify(prefs).updateLastHint(0, today) + whenever(prefs.lastHintNumber).thenReturn(2) + assertNull(hintList.pop()) + } + + @Test + @Throws(Exception::class) + fun shouldShow() { + whenever(prefs.lastHintTimestamp).thenReturn(today) + assertFalse(hintList.shouldShow()) + whenever(prefs.lastHintTimestamp).thenReturn(yesterday) + assertTrue(hintList.shouldShow()) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java deleted file mode 100644 index 38b572ed6..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; -import org.mockito.*; - -import java.io.*; - -import static java.nio.file.Files.createTempDirectory; -import static org.apache.commons.io.FileUtils.deleteDirectory; -import static org.apache.commons.io.FileUtils.listFiles; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.COULD_NOT_EXPORT; -import static org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.COULD_NOT_GENERATE_BUG_REPORT; -import static org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior.Message.DATABASE_REPAIRED; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class ListHabitsBehaviorTest extends BaseUnitTest -{ - @Mock - private ListHabitsBehavior.DirFinder dirFinder; - - @Mock - private Preferences prefs; - - private ListHabitsBehavior behavior; - - @Mock - private ListHabitsBehavior.Screen screen; - - private Habit habit1, habit2; - - @Captor - ArgumentCaptor picker; - - @Mock - private ListHabitsBehavior.BugReporter bugReporter; - - @Override - @Before - public void setUp() throws Exception - { - super.setUp(); - habit1 = fixtures.createShortHabit(); - habit2 = fixtures.createNumericalHabit(); - habitList.add(habit1); - habitList.add(habit2); - clearInvocations(habitList); - - behavior = new ListHabitsBehavior(habitList, dirFinder, taskRunner, screen, - commandRunner, prefs, bugReporter); - } - - @Test - public void testOnEdit() - { - behavior.onEdit(habit2, DateUtils.getToday()); - verify(screen).showNumberPicker(eq(0.1), eq("miles"), picker.capture()); - picker.getValue().onNumberPicked(100); - Timestamp today = DateUtils.getTodayWithOffset(); - assertThat(habit2.getComputedEntries().get(today).getValue(), equalTo(100000)); - } - - @Test - public void testOnExportCSV() throws Exception - { - File outputDir = createTempDirectory("CSV").toFile(); - when(dirFinder.getCSVOutputDir()).thenReturn(outputDir); - behavior.onExportCSV(); - verify(screen).showSendFileScreen(any()); - assertThat(listFiles(outputDir, null, false).size(), equalTo(1)); - deleteDirectory(outputDir); - } - - @Test - public void testOnExportCSV_fail() throws Exception - { - File outputDir = createTempDirectory("CSV").toFile(); - outputDir.setWritable(false); - when(dirFinder.getCSVOutputDir()).thenReturn(outputDir); - behavior.onExportCSV(); - verify(screen).showMessage(COULD_NOT_EXPORT); - assertTrue(outputDir.delete()); - } - - @Test - public void testOnHabitClick() - { - behavior.onClickHabit(habit1); - verify(screen).showHabitScreen(habit1); - } - - @Test - public void testOnHabitReorder() - { - Habit from = habit1; - Habit to = habit2; - behavior.onReorderHabit(from, to); - verify(habitList).reorder(from, to); - } - - @Test - public void testOnRepairDB() - { - behavior.onRepairDB(); - verify(habitList).repair(); - verify(screen).showMessage(DATABASE_REPAIRED); - } - - @Test - public void testOnSendBugReport() throws IOException - { - when(bugReporter.getBugReport()).thenReturn("hello"); - behavior.onSendBugReport(); - verify(bugReporter).dumpBugReportToFile(); - verify(screen).showSendBugReportToDeveloperScreen("hello"); - - when(bugReporter.getBugReport()).thenThrow(new IOException()); - behavior.onSendBugReport(); - verify(screen).showMessage(COULD_NOT_GENERATE_BUG_REPORT); - - } - - @Test - public void testOnStartup_firstLaunch() - { - Timestamp today = DateUtils.getToday(); - - when(prefs.isFirstRun()).thenReturn(true); - behavior.onStartup(); - verify(prefs).setFirstRun(false); - verify(prefs).updateLastHint(-1, today); - verify(screen).showIntroScreen(); - } - - @Test - public void testOnStartup_notFirstLaunch() - { - when(prefs.isFirstRun()).thenReturn(false); - behavior.onStartup(); - verify(prefs).incrementLaunchCount(); - } - - @Test - public void testOnToggle() - { - assertTrue(habit1.isCompletedToday()); - behavior.onToggle(habit1, DateUtils.getToday(), Entry.NO); - assertFalse(habit1.isCompletedToday()); - } - -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt new file mode 100644 index 000000000..2f1f3c4f6 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehaviorTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list + +import com.nhaarman.mockitokotlin2.KArgumentCaptor +import com.nhaarman.mockitokotlin2.argumentCaptor +import com.nhaarman.mockitokotlin2.clearInvocations +import com.nhaarman.mockitokotlin2.eq +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.apache.commons.io.FileUtils +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.core.IsEqual.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday +import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers +import java.io.IOException +import java.nio.file.Files + +class ListHabitsBehaviorTest : BaseUnitTest() { + private val dirFinder: ListHabitsBehavior.DirFinder = mock() + + private val prefs: Preferences = mock() + private lateinit var behavior: ListHabitsBehavior + + private val screen: ListHabitsBehavior.Screen = mock() + private lateinit var habit1: Habit + private lateinit var habit2: Habit + + var picker: KArgumentCaptor = argumentCaptor() + + private val bugReporter: ListHabitsBehavior.BugReporter = mock() + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit1 = fixtures.createShortHabit() + habit2 = fixtures.createNumericalHabit() + habitList.add(habit1) + habitList.add(habit2) + clearInvocations(habitList) + behavior = ListHabitsBehavior( + habitList, + dirFinder, + taskRunner, + screen, + commandRunner, + prefs, + bugReporter + ) + } + + @Test + fun testOnEdit() { + behavior.onEdit(habit2, getToday()) + verify(screen).showNumberPicker(eq(0.1), eq("miles"), picker.capture()) + picker.lastValue.onNumberPicked(100.0) + val today = getTodayWithOffset() + assertThat(habit2.computedEntries.get(today).value, equalTo(100000)) + } + + @Test + @Throws(Exception::class) + fun testOnExportCSV() { + val outputDir = Files.createTempDirectory("CSV").toFile() + whenever(dirFinder.csvOutputDir).thenReturn(outputDir) + behavior.onExportCSV() + verify(screen).showSendFileScreen(ArgumentMatchers.any()) + assertThat(FileUtils.listFiles(outputDir, null, false).size, equalTo(1)) + FileUtils.deleteDirectory(outputDir) + } + + @Test + @Throws(Exception::class) + fun testOnExportCSV_fail() { + val outputDir = Files.createTempDirectory("CSV").toFile() + outputDir.setWritable(false) + whenever(dirFinder.csvOutputDir).thenReturn(outputDir) + behavior.onExportCSV() + verify(screen).showMessage(ListHabitsBehavior.Message.COULD_NOT_EXPORT) + assertTrue(outputDir.delete()) + } + + @Test + fun testOnHabitClick() { + behavior.onClickHabit(habit1) + verify(screen).showHabitScreen(habit1) + } + + @Test + fun testOnHabitReorder() { + val from = habit1 + val to = habit2 + behavior.onReorderHabit(from, to) + verify(habitList).reorder(from, to) + } + + @Test + fun testOnRepairDB() { + behavior.onRepairDB() + verify(habitList).repair() + verify(screen).showMessage(ListHabitsBehavior.Message.DATABASE_REPAIRED) + } + + @Test + @Throws(IOException::class) + fun testOnSendBugReport() { + whenever(bugReporter.bugReport).thenReturn("hello") + behavior.onSendBugReport() + verify(bugReporter).dumpBugReportToFile() + verify(screen).showSendBugReportToDeveloperScreen("hello") + whenever(bugReporter.bugReport).thenThrow(IOException()) + behavior.onSendBugReport() + verify(screen).showMessage(ListHabitsBehavior.Message.COULD_NOT_GENERATE_BUG_REPORT) + } + + @Test + fun testOnStartup_firstLaunch() { + val today = getToday() + whenever(prefs.isFirstRun).thenReturn(true) + behavior.onStartup() + verify(prefs).isFirstRun = false + verify(prefs).updateLastHint(-1, today) + verify(screen).showIntroScreen() + } + + @Test + fun testOnStartup_notFirstLaunch() { + whenever(prefs.isFirstRun).thenReturn(false) + behavior.onStartup() + verify(prefs).incrementLaunchCount() + } + + @Test + fun testOnToggle() { + assertTrue(habit1.isCompletedToday()) + behavior.onToggle(habit1, getToday(), Entry.NO) + assertFalse(habit1.isCompletedToday()) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsMenuBehaviorTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsMenuBehaviorTest.java deleted file mode 100644 index ec8603179..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsMenuBehaviorTest.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; -import org.junit.*; -import org.mockito.*; - -import static junit.framework.TestCase.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import static org.isoron.uhabits.core.models.HabitList.Order.*; -import static org.mockito.Mockito.*; - -public class ListHabitsMenuBehaviorTest extends BaseUnitTest -{ - private ListHabitsMenuBehavior behavior; - - @Mock - private ListHabitsMenuBehavior.Screen screen; - - @Mock - private ListHabitsMenuBehavior.Adapter adapter; - - @Mock - private Preferences prefs; - - @Mock - private ThemeSwitcher themeSwitcher; - - @Captor - private ArgumentCaptor matcherCaptor; - - @Captor - private ArgumentCaptor orderCaptor; - - @Captor - private ArgumentCaptor secondaryOrderCaptor; - - @Override - public void setUp() throws Exception - { - super.setUp(); - behavior = - new ListHabitsMenuBehavior(screen, adapter, prefs, themeSwitcher); - clearInvocations(adapter); - } - - @Test - public void testInitialFilter() - { - when(prefs.getShowArchived()).thenReturn(true); - when(prefs.getShowCompleted()).thenReturn(true); - - behavior = - new ListHabitsMenuBehavior(screen, adapter, prefs, themeSwitcher); - verify(adapter).setFilter(matcherCaptor.capture()); - verify(adapter).refresh(); - verifyNoMoreInteractions(adapter); - clearInvocations(adapter); - - assertTrue(matcherCaptor.getValue().isArchivedAllowed()); - assertTrue(matcherCaptor.getValue().isCompletedAllowed()); - - when(prefs.getShowArchived()).thenReturn(false); - when(prefs.getShowCompleted()).thenReturn(false); - - behavior = - new ListHabitsMenuBehavior(screen, adapter, prefs, themeSwitcher); - verify(adapter).setFilter(matcherCaptor.capture()); - verify(adapter).refresh(); - verifyNoMoreInteractions(adapter); - - assertFalse(matcherCaptor.getValue().isArchivedAllowed()); - assertFalse(matcherCaptor.getValue().isCompletedAllowed()); - } - -// @Test -// public void testOnCreateHabit() -// { -// behavior.onCreateHabit(); -// verify(screen).showCreateHabitScreen(); -// } - - @Test - public void testOnSortByColor() - { - behavior.onSortByColor(); - verify(adapter).setPrimaryOrder(orderCaptor.capture()); - assertThat(orderCaptor.getValue(), equalTo(BY_COLOR_ASC)); - } - - @Test - public void testOnSortManually() - { - behavior.onSortByManually(); - verify(adapter).setPrimaryOrder(orderCaptor.capture()); - assertThat(orderCaptor.getValue(), equalTo(BY_POSITION)); - } - - @Test - public void testOnSortScore() - { - behavior.onSortByScore(); - verify(adapter).setPrimaryOrder(orderCaptor.capture()); - assertThat(orderCaptor.getValue(), equalTo(BY_SCORE_DESC)); - } - - @Test - public void testOnSortName() - { - behavior.onSortByName(); - verify(adapter).setPrimaryOrder(orderCaptor.capture()); - assertThat(orderCaptor.getValue(), equalTo(BY_NAME_ASC)); - } - - @Test - public void testOnSortStatus() - { - when(adapter.getPrimaryOrder()).thenReturn(BY_NAME_ASC); - - behavior.onSortByStatus(); - verify(adapter).setPrimaryOrder(orderCaptor.capture()); - verify(adapter).setSecondaryOrder(secondaryOrderCaptor.capture()); - assertThat(orderCaptor.getValue(), equalTo(BY_STATUS_ASC)); - assertThat(secondaryOrderCaptor.getValue(), equalTo(BY_NAME_ASC)); - } - - @Test - public void testOnSortStatusToggle() - { - when(adapter.getPrimaryOrder()).thenReturn(BY_STATUS_ASC); - - behavior.onSortByStatus(); - - verify(adapter).setPrimaryOrder(orderCaptor.capture()); - verify(adapter, never()).setSecondaryOrder(any()); - assertThat(orderCaptor.getValue(), equalTo(BY_STATUS_DESC)); - } - - @Test - public void testOnToggleShowArchived() - { - behavior.onToggleShowArchived(); - verify(adapter).setFilter(matcherCaptor.capture()); - assertTrue(matcherCaptor.getValue().isArchivedAllowed()); - - clearInvocations(adapter); - - behavior.onToggleShowArchived(); - verify(adapter).setFilter(matcherCaptor.capture()); - assertFalse(matcherCaptor.getValue().isArchivedAllowed()); - } - - @Test - public void testOnToggleShowCompleted() - { - behavior.onToggleShowCompleted(); - verify(adapter).setFilter(matcherCaptor.capture()); - assertTrue(matcherCaptor.getValue().isCompletedAllowed()); - - clearInvocations(adapter); - - behavior.onToggleShowCompleted(); - verify(adapter).setFilter(matcherCaptor.capture()); - assertFalse(matcherCaptor.getValue().isCompletedAllowed()); - } - - @Test - public void testOnViewAbout() - { - behavior.onViewAbout(); - verify(screen).showAboutScreen(); - } - - @Test - public void testOnViewFAQ() - { - behavior.onViewFAQ(); - verify(screen).showFAQScreen(); - } - - @Test - public void testOnViewSettings() - { - behavior.onViewSettings(); - verify(screen).showSettingsScreen(); - } - - @Test - public void testOnToggleNightMode() - { - behavior.onToggleNightMode(); - verify(themeSwitcher).toggleNightMode(); - verify(screen).applyTheme(); - } -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsMenuBehaviorTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsMenuBehaviorTest.kt new file mode 100644 index 000000000..463320d1f --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsMenuBehaviorTest.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list + +import com.nhaarman.mockitokotlin2.KArgumentCaptor +import com.nhaarman.mockitokotlin2.argumentCaptor +import com.nhaarman.mockitokotlin2.clearInvocations +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.never +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions +import com.nhaarman.mockitokotlin2.whenever +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.HabitList +import org.isoron.uhabits.core.models.HabitMatcher +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.ui.ThemeSwitcher +import org.junit.Test +import org.mockito.ArgumentMatchers + +class ListHabitsMenuBehaviorTest : BaseUnitTest() { + private lateinit var behavior: ListHabitsMenuBehavior + + private val screen: ListHabitsMenuBehavior.Screen = mock() + + private val adapter: ListHabitsMenuBehavior.Adapter = mock() + + private val prefs: Preferences = mock() + + private val themeSwitcher: ThemeSwitcher = mock() + + private val matcherCaptor: KArgumentCaptor = argumentCaptor() + + private val orderCaptor: KArgumentCaptor = argumentCaptor() + + private val secondaryOrderCaptor: KArgumentCaptor = argumentCaptor() + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + behavior = ListHabitsMenuBehavior(screen, adapter, prefs, themeSwitcher) + clearInvocations(adapter) + } + + @Test + fun testInitialFilter() { + whenever(prefs.showArchived).thenReturn(true) + whenever(prefs.showCompleted).thenReturn(true) + behavior = ListHabitsMenuBehavior(screen, adapter, prefs, themeSwitcher) + verify(adapter).setFilter(matcherCaptor.capture()) + verify(adapter).refresh() + verifyNoMoreInteractions(adapter) + clearInvocations(adapter) + assertTrue(matcherCaptor.lastValue.isArchivedAllowed) + assertTrue(matcherCaptor.lastValue.isCompletedAllowed) + whenever(prefs.showArchived).thenReturn(false) + whenever(prefs.showCompleted).thenReturn(false) + behavior = ListHabitsMenuBehavior(screen, adapter, prefs, themeSwitcher) + verify(adapter).setFilter(matcherCaptor.capture()) + verify(adapter).refresh() + verifyNoMoreInteractions(adapter) + assertFalse(matcherCaptor.lastValue.isArchivedAllowed) + assertFalse(matcherCaptor.lastValue.isCompletedAllowed) + } + + @Test + fun testOnSortByColor() { + behavior.onSortByColor() + verify(adapter).primaryOrder = orderCaptor.capture() + assertThat(orderCaptor.lastValue, equalTo(HabitList.Order.BY_COLOR_ASC)) + } + + @Test + fun testOnSortManually() { + behavior.onSortByManually() + verify(adapter).primaryOrder = orderCaptor.capture() + assertThat(orderCaptor.lastValue, equalTo(HabitList.Order.BY_POSITION)) + } + + @Test + fun testOnSortScore() { + behavior.onSortByScore() + verify(adapter).primaryOrder = orderCaptor.capture() + assertThat(orderCaptor.lastValue, equalTo(HabitList.Order.BY_SCORE_DESC)) + } + + @Test + fun testOnSortName() { + behavior.onSortByName() + verify(adapter).primaryOrder = orderCaptor.capture() + assertThat(orderCaptor.lastValue, equalTo(HabitList.Order.BY_NAME_ASC)) + } + + @Test + fun testOnSortStatus() { + whenever(adapter.primaryOrder).thenReturn(HabitList.Order.BY_NAME_ASC) + behavior.onSortByStatus() + verify(adapter).primaryOrder = orderCaptor.capture() + verify(adapter).setSecondaryOrder(secondaryOrderCaptor.capture()) + assertThat(orderCaptor.lastValue, equalTo(HabitList.Order.BY_STATUS_ASC)) + assertThat(secondaryOrderCaptor.lastValue, equalTo(HabitList.Order.BY_NAME_ASC)) + } + + @Test + fun testOnSortStatusToggle() { + whenever(adapter.primaryOrder).thenReturn(HabitList.Order.BY_STATUS_ASC) + behavior.onSortByStatus() + verify(adapter).primaryOrder = orderCaptor.capture() + verify(adapter, never()).setSecondaryOrder(ArgumentMatchers.any()) + assertThat(orderCaptor.lastValue, equalTo(HabitList.Order.BY_STATUS_DESC)) + } + + @Test + fun testOnToggleShowArchived() { + behavior.onToggleShowArchived() + verify(adapter).setFilter(matcherCaptor.capture()) + assertTrue(matcherCaptor.lastValue.isArchivedAllowed) + clearInvocations(adapter) + behavior.onToggleShowArchived() + verify(adapter).setFilter(matcherCaptor.capture()) + assertFalse(matcherCaptor.lastValue.isArchivedAllowed) + } + + @Test + fun testOnToggleShowCompleted() { + behavior.onToggleShowCompleted() + verify(adapter).setFilter(matcherCaptor.capture()) + assertTrue(matcherCaptor.lastValue.isCompletedAllowed) + clearInvocations(adapter) + behavior.onToggleShowCompleted() + verify(adapter).setFilter(matcherCaptor.capture()) + assertFalse(matcherCaptor.lastValue.isCompletedAllowed) + } + + @Test + fun testOnViewAbout() { + behavior.onViewAbout() + verify(screen).showAboutScreen() + } + + @Test + fun testOnViewFAQ() { + behavior.onViewFAQ() + verify(screen).showFAQScreen() + } + + @Test + fun testOnViewSettings() { + behavior.onViewSettings() + verify(screen).showSettingsScreen() + } + + @Test + fun testOnToggleNightMode() { + behavior.onToggleNightMode() + verify(themeSwitcher).toggleNightMode() + verify(screen).applyTheme() + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsSelectionMenuBehaviorTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsSelectionMenuBehaviorTest.java deleted file mode 100644 index 86d5e913f..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsSelectionMenuBehaviorTest.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.ui.callbacks.*; -import org.junit.*; -import org.mockito.*; - -import java.util.*; - -import static java.util.Arrays.*; -import static java.util.Collections.*; -import static junit.framework.TestCase.*; -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; -import static org.mockito.Mockito.*; - -public class ListHabitsSelectionMenuBehaviorTest extends BaseUnitTest -{ - @Mock - private ListHabitsSelectionMenuBehavior.Screen screen; - - @Mock - private ListHabitsSelectionMenuBehavior.Adapter adapter; - - private ListHabitsSelectionMenuBehavior behavior; - - private Habit habit1, habit2, habit3; - - @Captor - private ArgumentCaptor colorPickerCallback; - - @Captor - private ArgumentCaptor deleteCallback; - - @Test - public void canArchive() throws Exception - { - when(adapter.getSelected()).thenReturn(asList(habit1, habit2)); - assertFalse(behavior.canArchive()); - - when(adapter.getSelected()).thenReturn(asList(habit2, habit3)); - assertTrue(behavior.canArchive()); - } - - @Test - public void canEdit() throws Exception - { - when(adapter.getSelected()).thenReturn(singletonList(habit1)); - assertTrue(behavior.canEdit()); - - when(adapter.getSelected()).thenReturn(asList(habit1, habit2)); - assertFalse(behavior.canEdit()); - } - - @Test - public void canUnarchive() throws Exception - { - when(adapter.getSelected()).thenReturn(asList(habit1, habit2)); - assertFalse(behavior.canUnarchive()); - - when(adapter.getSelected()).thenReturn(singletonList(habit1)); - assertTrue(behavior.canUnarchive()); - } - - @Test - public void onArchiveHabits() throws Exception - { - assertFalse(habit2.isArchived()); - when(adapter.getSelected()).thenReturn(singletonList(habit2)); - behavior.onArchiveHabits(); - assertTrue(habit2.isArchived()); - } - - @Test - public void onChangeColor() throws Exception - { - assertThat(habit1.getColor(), equalTo(new PaletteColor(8))); - assertThat(habit2.getColor(), equalTo(new PaletteColor(8))); - when(adapter.getSelected()).thenReturn(asList(habit1, habit2)); - - behavior.onChangeColor(); - - verify(screen).showColorPicker(eq(new PaletteColor(8)), colorPickerCallback.capture()); - colorPickerCallback.getValue().onColorPicked(new PaletteColor(30)); - assertThat(habit1.getColor(), equalTo(new PaletteColor(30))); - } - - @Test - public void onDeleteHabits() throws Exception - { - Long id = habit1.getId(); - assertNotNull(id); - assertNotNull(habitList.getById(id)); - when(adapter.getSelected()).thenReturn(singletonList(habit1)); - - behavior.onDeleteHabits(); - - verify(screen).showDeleteConfirmationScreen(deleteCallback.capture(), eq(1)); - deleteCallback.getValue().onConfirmed(); - assertNull(habitList.getById(id)); - } - - @Test - public void onEditHabits() throws Exception - { - List selected = asList(habit1, habit2); - when(adapter.getSelected()).thenReturn(selected); - behavior.onEditHabits(); - verify(screen).showEditHabitsScreen(selected); - } - - @Test - public void onUnarchiveHabits() throws Exception - { - assertTrue(habit1.isArchived()); - when(adapter.getSelected()).thenReturn(singletonList(habit1)); - behavior.onUnarchiveHabits(); - assertFalse(habit1.isArchived()); - } - - @Override - public void setUp() throws Exception - { - super.setUp(); - - habit1 = fixtures.createShortHabit(); - habit1.setArchived(true); - habit2 = fixtures.createShortHabit(); - habit3 = fixtures.createShortHabit(); - habitList.add(habit1); - habitList.add(habit2); - habitList.add(habit3); - - behavior = - new ListHabitsSelectionMenuBehavior(habitList, screen, adapter, - commandRunner); - } - -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsSelectionMenuBehaviorTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsSelectionMenuBehaviorTest.kt new file mode 100644 index 000000000..5974d3ad1 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsSelectionMenuBehaviorTest.kt @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.list + +import com.nhaarman.mockitokotlin2.KArgumentCaptor +import com.nhaarman.mockitokotlin2.argumentCaptor +import com.nhaarman.mockitokotlin2.eq +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertNull +import junit.framework.Assert.assertTrue +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.PaletteColor +import org.isoron.uhabits.core.ui.callbacks.OnColorPickedCallback +import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback +import org.junit.Test + +class ListHabitsSelectionMenuBehaviorTest : BaseUnitTest() { + private val screen: ListHabitsSelectionMenuBehavior.Screen = mock() + + private val adapter: ListHabitsSelectionMenuBehavior.Adapter = mock() + private lateinit var behavior: ListHabitsSelectionMenuBehavior + private lateinit var habit1: Habit + private lateinit var habit2: Habit + private lateinit var habit3: Habit + + private val colorPickerCallback: KArgumentCaptor = argumentCaptor() + + private val deleteCallback: KArgumentCaptor = argumentCaptor() + + @Test + @Throws(Exception::class) + fun canArchive() { + whenever(adapter.selected).thenReturn(listOf(habit1, habit2)) + assertFalse(behavior.canArchive()) + whenever(adapter.selected).thenReturn(listOf(habit2, habit3)) + assertTrue(behavior.canArchive()) + } + + @Test + @Throws(Exception::class) + fun canEdit() { + whenever(adapter.selected).thenReturn(listOf(habit1)) + assertTrue(behavior.canEdit()) + whenever(adapter.selected).thenReturn(listOf(habit1, habit2)) + assertFalse(behavior.canEdit()) + } + + @Test + @Throws(Exception::class) + fun canUnarchive() { + whenever(adapter.selected).thenReturn(listOf(habit1, habit2)) + assertFalse(behavior.canUnarchive()) + whenever(adapter.selected).thenReturn(listOf(habit1)) + assertTrue(behavior.canUnarchive()) + } + + @Test + @Throws(Exception::class) + fun onArchiveHabits() { + assertFalse(habit2.isArchived) + whenever(adapter.selected).thenReturn(listOf(habit2)) + behavior.onArchiveHabits() + assertTrue(habit2.isArchived) + } + + @Test + @Throws(Exception::class) + fun onChangeColor() { + assertThat(habit1.color, equalTo(PaletteColor(8))) + assertThat(habit2.color, equalTo(PaletteColor(8))) + whenever(adapter.selected).thenReturn(listOf(habit1, habit2)) + behavior.onChangeColor() + verify(screen) + .showColorPicker(eq(PaletteColor(8)), colorPickerCallback.capture()) + colorPickerCallback.lastValue.onColorPicked(PaletteColor(30)) + assertThat(habit1.color, equalTo(PaletteColor(30))) + } + + @Test + @Throws(Exception::class) + fun onDeleteHabits() { + val id = habit1.id!! + habitList.getById(id)!! + whenever(adapter.selected).thenReturn(listOf(habit1)) + behavior.onDeleteHabits() + verify(screen).showDeleteConfirmationScreen(deleteCallback.capture(), eq(1)) + deleteCallback.lastValue.onConfirmed() + assertNull(habitList.getById(id)) + } + + @Test + @Throws(Exception::class) + fun onEditHabits() { + val selected: List = listOf(habit1, habit2) + whenever(adapter.selected).thenReturn(selected) + behavior.onEditHabits() + verify(screen).showEditHabitsScreen(selected) + } + + @Test + @Throws(Exception::class) + fun onUnarchiveHabits() { + assertTrue(habit1.isArchived) + whenever(adapter.selected).thenReturn(listOf(habit1)) + behavior.onUnarchiveHabits() + assertFalse(habit1.isArchived) + } + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit1 = fixtures.createShortHabit() + habit1.isArchived = true + habit2 = fixtures.createShortHabit() + habit3 = fixtures.createShortHabit() + habitList.add(habit1) + habitList.add(habit2) + habitList.add(habit3) + behavior = ListHabitsSelectionMenuBehavior( + habitList, + screen, + adapter, + commandRunner + ) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.java deleted file mode 100644 index 07997521d..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.show; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; - -import java.io.*; - -import static java.nio.file.Files.*; -import static org.apache.commons.io.FileUtils.*; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.*; - -public class ShowHabitMenuPresenterTest extends BaseUnitTest -{ - private ShowHabitMenuPresenter.System system; - - private ShowHabitMenuPresenter.Screen screen; - - private Habit habit; - - private ShowHabitMenuPresenter menu; - - @Override - public void setUp() throws Exception - { - super.setUp(); - system = mock(ShowHabitMenuPresenter.System.class); - screen = mock(ShowHabitMenuPresenter.Screen.class); - - habit = fixtures.createShortHabit(); - menu = new ShowHabitMenuPresenter(commandRunner, habit, habitList, screen, system, taskRunner); - } - - @Test - public void testOnEditHabit() - { - menu.onEditHabit(); - verify(screen).showEditHabitScreen(habit); - } - - @Test - public void testOnExport() throws Exception - { - File outputDir = createTempDirectory("CSV").toFile(); - when(system.getCSVOutputDir()).thenReturn(outputDir); - menu.onExportCSV(); - assertThat(listFiles(outputDir, null, false).size(), equalTo(1)); - deleteDirectory(outputDir); - } -} \ No newline at end of file diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.kt new file mode 100644 index 000000000..aac1cf320 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitMenuPresenterTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016-2021 Á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.core.ui.screens.habits.show + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import org.apache.commons.io.FileUtils +import org.hamcrest.CoreMatchers.equalTo +import org.hamcrest.MatcherAssert.assertThat +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Habit +import org.junit.Test +import java.nio.file.Files + +class ShowHabitMenuPresenterTest : BaseUnitTest() { + private lateinit var system: ShowHabitMenuPresenter.System + private lateinit var screen: ShowHabitMenuPresenter.Screen + private lateinit var habit: Habit + private lateinit var menu: ShowHabitMenuPresenter + + @Throws(Exception::class) + override fun setUp() { + super.setUp() + system = mock() + screen = mock() + habit = fixtures.createShortHabit() + menu = ShowHabitMenuPresenter( + commandRunner, + habit, + habitList, + screen, + system, + taskRunner + ) + } + + @Test + fun testOnEditHabit() { + menu.onEditHabit() + verify(screen).showEditHabitScreen(habit) + } + + @Test + @Throws(Exception::class) + fun testOnExport() { + val outputDir = Files.createTempDirectory("CSV").toFile() + whenever(system.getCSVOutputDir()).thenReturn(outputDir) + menu.onExportCSV() + assertThat(FileUtils.listFiles(outputDir, null, false).size, equalTo(1)) + FileUtils.deleteDirectory(outputDir) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt index c513b77d2..ff608a49b 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt @@ -29,11 +29,11 @@ import java.util.Locale class BarChartTest { val base = "views/BarChart" val today = LocalDate(2015, 1, 25) - val fmt = JavaLocalDateFormatter(Locale.US) + private val fmt = JavaLocalDateFormatter(Locale.US) val theme = LightTheme() val component = BarChart(theme, fmt) - val axis = (0..100).map { today.minus(it) } - val series1 = listOf(200.0, 0.0, 150.0, 137.0, 0.0, 0.0, 500.0, 30.0, 100.0, 0.0, 300.0) + private val axis = (0..100).map { today.minus(it) } + private val series1 = listOf(200.0, 0.0, 150.0, 137.0, 0.0, 0.0, 500.0, 30.0, 100.0, 0.0, 300.0) init { component.axis = axis diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt index ef5b9fb66..4a511c04b 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/HistoryChartTest.kt @@ -17,7 +17,7 @@ * with this program. If not, see . */ -package org.isoron.uhabits.components +package org.isoron.uhabits.core.ui.views import kotlinx.coroutines.runBlocking import org.isoron.platform.gui.assertRenders @@ -26,15 +26,10 @@ import org.isoron.platform.time.DayOfWeek.SUNDAY import org.isoron.platform.time.JavaLocalDateFormatter import org.isoron.platform.time.LocalDate import org.isoron.uhabits.core.models.PaletteColor -import org.isoron.uhabits.core.ui.views.DarkTheme -import org.isoron.uhabits.core.ui.views.HistoryChart import org.isoron.uhabits.core.ui.views.HistoryChart.Square.DIMMED import org.isoron.uhabits.core.ui.views.HistoryChart.Square.HATCHED import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON -import org.isoron.uhabits.core.ui.views.LightTheme -import org.isoron.uhabits.core.ui.views.OnDateClickedListener -import org.isoron.uhabits.core.ui.views.WidgetTheme import org.junit.Test import org.mockito.Mockito.mock import org.mockito.Mockito.reset @@ -45,7 +40,7 @@ import java.util.Locale class HistoryChartTest { val base = "views/HistoryChart" - val dateClickedListener = mock(OnDateClickedListener::class.java) + private val dateClickedListener = mock(OnDateClickedListener::class.java)!! val view = HistoryChart( today = LocalDate(2015, 1, 25), diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.java deleted file mode 100644 index f99554787..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.ui.widgets; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.commands.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.ui.*; -import org.isoron.uhabits.core.utils.*; -import org.junit.*; - -import java.util.*; - -import static org.isoron.uhabits.core.models.Entry.*; -import static org.mockito.Mockito.*; - -public class WidgetBehaviorTest extends BaseUnitTest -{ - private NotificationTray notificationTray; - - private CommandRunner commandRunner; - - private Preferences preferences; - - private WidgetBehavior behavior; - - private Habit habit; - - private Timestamp today; - - @Before - @Override - public void setUp() throws Exception - { - super.setUp(); - habit = fixtures.createEmptyHabit(); - commandRunner = mock(CommandRunner.class); - notificationTray = mock(NotificationTray.class); - preferences = mock(Preferences.class); - behavior = new WidgetBehavior(habitList, commandRunner, notificationTray, preferences); - today = DateUtils.getTodayWithOffset(); - } - - @Test - public void testOnAddRepetition() - { - behavior.onAddRepetition(habit, today); - verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, today, YES_MANUAL) - ); - verify(notificationTray).cancel(habit); - verifyZeroInteractions(preferences); - } - - @Test - public void testOnRemoveRepetition() - { - behavior.onRemoveRepetition(habit, today); - verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, today, NO) - ); - verify(notificationTray).cancel(habit); - verifyZeroInteractions(preferences); - } - - @Test - public void testOnToggleRepetition() - { - for (boolean skipEnabled : Arrays.asList(true, false)) - for (int currentValue : Arrays.asList(NO, YES_MANUAL, YES_AUTO, SKIP)) - { - when(preferences.isSkipEnabled()).thenReturn(skipEnabled); - - int nextValue; - if(skipEnabled) nextValue = Entry.Companion.nextToggleValueWithSkip(currentValue); - else nextValue = Entry.Companion.nextToggleValueWithoutSkip(currentValue); - - habit.getOriginalEntries().add(new Entry(today, currentValue)); - behavior.onToggleRepetition(habit, today); - verify(preferences).isSkipEnabled(); - verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, today, nextValue) - ); - verify(notificationTray).cancel(habit); - reset(preferences, commandRunner, notificationTray); - } - } - - @Test - public void testOnIncrement() - { - habit = fixtures.createNumericalHabit(); - habit.getOriginalEntries().add(new Entry(today, 500)); - habit.recompute(); - - behavior.onIncrement(habit, today, 100); - verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, today, 600) - ); - verify(notificationTray).cancel(habit); - verifyZeroInteractions(preferences); - } - - @Test - public void testOnDecrement() - { - habit = fixtures.createNumericalHabit(); - habit.getOriginalEntries().add(new Entry(today, 500)); - habit.recompute(); - - behavior.onDecrement(habit, today, 100); - verify(commandRunner).run( - new CreateRepetitionCommand(habitList, habit, today, 400) - ); - verify(notificationTray).cancel(habit); - verifyZeroInteractions(preferences); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.kt new file mode 100644 index 000000000..e02e5e043 --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/widgets/WidgetBehaviorTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016-2021 Á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.core.ui.widgets + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.reset +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.verifyZeroInteractions +import com.nhaarman.mockitokotlin2.whenever +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.commands.CreateRepetitionCommand +import org.isoron.uhabits.core.models.Entry +import org.isoron.uhabits.core.models.Entry.Companion.nextToggleValueWithSkip +import org.isoron.uhabits.core.models.Entry.Companion.nextToggleValueWithoutSkip +import org.isoron.uhabits.core.models.Habit +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.preferences.Preferences +import org.isoron.uhabits.core.ui.NotificationTray +import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset +import org.junit.Before +import org.junit.Test + +class WidgetBehaviorTest : BaseUnitTest() { + private lateinit var notificationTray: NotificationTray + private lateinit var preferences: Preferences + private lateinit var behavior: WidgetBehavior + private lateinit var habit: Habit + private lateinit var today: Timestamp + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + habit = fixtures.createEmptyHabit() + commandRunner = mock() + notificationTray = mock() + preferences = mock() + behavior = WidgetBehavior(habitList, commandRunner, notificationTray, preferences) + today = getTodayWithOffset() + } + + @Test + fun testOnAddRepetition() { + behavior.onAddRepetition(habit, today) + verify(commandRunner).run( + CreateRepetitionCommand(habitList, habit, today, Entry.YES_MANUAL) + ) + verify(notificationTray).cancel(habit) + verifyZeroInteractions(preferences) + } + + @Test + fun testOnRemoveRepetition() { + behavior.onRemoveRepetition(habit, today) + verify(commandRunner).run( + CreateRepetitionCommand(habitList, habit, today, Entry.NO) + ) + verify(notificationTray).cancel(habit) + verifyZeroInteractions(preferences) + } + + @Test + fun testOnToggleRepetition() { + for (skipEnabled in listOf(true, false)) for ( + currentValue in listOf( + Entry.NO, + Entry.YES_MANUAL, + Entry.YES_AUTO, + Entry.SKIP + ) + ) { + whenever(preferences.isSkipEnabled).thenReturn(skipEnabled) + val nextValue: Int = if (skipEnabled) nextToggleValueWithSkip(currentValue) else nextToggleValueWithoutSkip( + currentValue + ) + habit.originalEntries.add(Entry(today, currentValue)) + behavior.onToggleRepetition(habit, today) + verify(preferences).isSkipEnabled + verify(commandRunner).run( + CreateRepetitionCommand(habitList, habit, today, nextValue) + ) + verify(notificationTray).cancel( + habit + ) + reset(preferences, commandRunner, notificationTray) + } + } + + @Test + fun testOnIncrement() { + habit = fixtures.createNumericalHabit() + habit.originalEntries.add(Entry(today, 500)) + habit.recompute() + behavior.onIncrement(habit, today, 100) + verify(commandRunner).run( + CreateRepetitionCommand(habitList, habit, today, 600) + ) + verify(notificationTray).cancel(habit) + verifyZeroInteractions(preferences) + } + + @Test + fun testOnDecrement() { + habit = fixtures.createNumericalHabit() + habit.originalEntries.add(Entry(today, 500)) + habit.recompute() + behavior.onDecrement(habit, today, 100) + verify(commandRunner).run( + CreateRepetitionCommand(habitList, habit, today, 400) + ) + verify(notificationTray).cancel(habit) + verifyZeroInteractions(preferences) + } +} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.java b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.java deleted file mode 100644 index 13f102d49..000000000 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2016-2021 Á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.core.utils; - -import org.isoron.uhabits.core.*; -import org.isoron.uhabits.core.models.*; -import org.junit.*; - -import java.util.*; - -import static java.util.Calendar.*; -import static junit.framework.Assert.assertEquals; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.*; -import static org.isoron.uhabits.core.utils.DateUtils.*; - -public class DateUtilsTest extends BaseUnitTest -{ - - int firstWeekday = SUNDAY; - - @Before - @Override - public void setUp() throws Exception - { - super.setUp(); - DateUtils.setFixedLocale(Locale.US); - } - - @Test - public void testFormatHeaderDate() - { - long timestamp = unixTime(2015, DECEMBER, 31); - GregorianCalendar date = new Timestamp(timestamp).toCalendar(); - String formatted = DateUtils.formatHeaderDate(date); - assertThat(formatted, equalTo("Thu\n31")); - } - - @Test - public void testTruncate_dayOfWeek() - { - DateUtils.TruncateField field = DateUtils.TruncateField.WEEK_NUMBER; - - long expected = unixTime(2015, Calendar.JANUARY, 11); - long t0 = unixTime(2015, Calendar.JANUARY, 11); - long t1 = unixTime(2015, Calendar.JANUARY, 16); - long t2 = unixTime(2015, Calendar.JANUARY, 17); - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - - expected = unixTime(2015, Calendar.JANUARY, 18); - t0 = unixTime(2015, Calendar.JANUARY, 18); - t1 = unixTime(2015, Calendar.JANUARY, 19); - t2 = unixTime(2015, Calendar.JANUARY, 24); - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - - - firstWeekday = WEDNESDAY; - expected = unixTime(2015, Calendar.JANUARY, 7); - t0 = unixTime(2015, Calendar.JANUARY, 7); - t1 = unixTime(2015, Calendar.JANUARY, 9); - t2 = unixTime(2015, Calendar.JANUARY, 13); - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - } - - @Test - public void testTruncate_month() - { - long expected = unixTime(2016, Calendar.JUNE, 1); - long t0 = unixTime(2016, Calendar.JUNE, 1); - long t1 = unixTime(2016, Calendar.JUNE, 15); - long t2 = unixTime(2016, Calendar.JUNE, 20); - - DateUtils.TruncateField field = DateUtils.TruncateField.MONTH; - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - - expected = unixTime(2016, DECEMBER, 1); - t0 = unixTime(2016, DECEMBER, 1); - t1 = unixTime(2016, DECEMBER, 15); - t2 = unixTime(2016, DECEMBER, 31); - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - } - - @Test - public void testTruncate_quarter() - { - DateUtils.TruncateField field = DateUtils.TruncateField.QUARTER; - - long expected = unixTime(2016, JANUARY, 1); - long t0 = unixTime(2016, JANUARY, 20); - long t1 = unixTime(2016, FEBRUARY, 15); - long t2 = unixTime(2016, MARCH, 30); - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - - expected = unixTime(2016, APRIL, 1); - t0 = unixTime(2016, APRIL, 1); - t1 = unixTime(2016, MAY, 30); - t2 = unixTime(2016, JUNE, 20); - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - } - - @Test - public void testTruncate_year() - { - DateUtils.TruncateField field = DateUtils.TruncateField.YEAR; - - long expected = unixTime(2016, JANUARY, 1); - long t0 = unixTime(2016, JANUARY, 1); - long t1 = unixTime(2016, FEBRUARY, 25); - long t2 = unixTime(2016, DECEMBER, 31); - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - - expected = unixTime(2017, JANUARY, 1); - t0 = unixTime(2017, JANUARY, 1); - t1 = unixTime(2017, MAY, 30); - t2 = unixTime(2017, DECEMBER, 31); - - assertThat(DateUtils.truncate(field, t0, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t1, firstWeekday), equalTo(expected)); - assertThat(DateUtils.truncate(field, t2, firstWeekday), equalTo(expected)); - } - - @Test - public void testMillisecondsUntilTomorrow() throws Exception - { - DateUtils.setFixedTimeZone(TimeZone.getTimeZone("GMT")); - - DateUtils.setFixedLocalTime(unixTime(2017, JANUARY, 1, 23, 59)); - assertThat(DateUtils.millisecondsUntilTomorrowWithOffset(), equalTo(MINUTE_LENGTH)); - - DateUtils.setFixedLocalTime(unixTime(2017, JANUARY, 1, 20, 0)); - assertThat(DateUtils.millisecondsUntilTomorrowWithOffset(), equalTo(4 * HOUR_LENGTH)); - - DateUtils.setStartDayOffset(3, 30); - DateUtils.setFixedLocalTime(unixTime(2017, JANUARY, 1, 23, 59)); - assertThat(DateUtils.millisecondsUntilTomorrowWithOffset(), equalTo(3 * HOUR_LENGTH + 31 * MINUTE_LENGTH)); - - DateUtils.setFixedLocalTime(unixTime(2017, JANUARY, 2, 1, 0)); - assertThat(DateUtils.millisecondsUntilTomorrowWithOffset(), equalTo(2 * HOUR_LENGTH + 30 * MINUTE_LENGTH)); - } - - @Test - public void testGetTodayWithOffset() throws Exception - { - assertThat(DateUtils.getTodayWithOffset(), equalTo(new Timestamp(FIXED_LOCAL_TIME))); - DateUtils.setStartDayOffset(9, 0); - assertThat( - DateUtils.getTodayWithOffset(), - equalTo(new Timestamp(FIXED_LOCAL_TIME - DAY_LENGTH))); - } - - @Test - public void testGetStartOfDayWithOffset() throws Exception - { - long timestamp = unixTime(2020, SEPTEMBER, 3); - assertThat( - DateUtils.getStartOfDayWithOffset(timestamp + HOUR_LENGTH), - equalTo(timestamp)); - DateUtils.setStartDayOffset(3, 30); - assertThat( - DateUtils.getStartOfDayWithOffset(timestamp + 3 * HOUR_LENGTH + 29 * MINUTE_LENGTH), - equalTo(timestamp - DAY_LENGTH)); - } - - @Test - public void test_applyTimezone() - { - DateUtils.setFixedTimeZone(TimeZone.getTimeZone("Australia/Sydney")); - assertEquals(applyTimezone(unixTime(2017, JULY, 30, 18, 0)), (unixTime(2017, JULY, 30, 8, 0))); - assertEquals(applyTimezone(unixTime(2017, SEPTEMBER, 30, 0, 0)), (unixTime(2017, SEPTEMBER, 29, 14, 0))); - assertEquals(applyTimezone(unixTime(2017, SEPTEMBER, 30, 10, 0)), (unixTime(2017, SEPTEMBER, 30, 0, 0))); - assertEquals(applyTimezone(unixTime(2017, SEPTEMBER, 30, 11, 0)), (unixTime(2017, SEPTEMBER, 30, 1, 0))); - assertEquals(applyTimezone(unixTime(2017, SEPTEMBER, 30, 12, 0)), (unixTime(2017, SEPTEMBER, 30, 2, 0))); - assertEquals(applyTimezone(unixTime(2017, SEPTEMBER, 30, 13, 0)), (unixTime(2017, SEPTEMBER, 30, 3, 0))); - assertEquals(applyTimezone(unixTime(2017, SEPTEMBER, 30, 22, 0)), (unixTime(2017, SEPTEMBER, 30, 12, 0))); - assertEquals(applyTimezone(unixTime(2017, SEPTEMBER, 30, 23, 0)), (unixTime(2017, SEPTEMBER, 30, 13, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 0, 0)), (unixTime(2017, SEPTEMBER, 30, 14, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 1, 0)), (unixTime(2017, SEPTEMBER, 30, 15, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 1, 59)), (unixTime(2017, SEPTEMBER, 30, 15, 59))); - // DST begins - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 3, 0)), (unixTime(2017, SEPTEMBER, 30, 16, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 4, 0)), (unixTime(2017, SEPTEMBER, 30, 17, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 5, 0)), (unixTime(2017, SEPTEMBER, 30, 18, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 11, 0)), (unixTime(2017, OCTOBER, 1, 0, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 12, 0)), (unixTime(2017, OCTOBER, 1, 1, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 13, 0)), (unixTime(2017, OCTOBER, 1, 2, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 14, 0)), (unixTime(2017, OCTOBER, 1, 3, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 15, 0)), (unixTime(2017, OCTOBER, 1, 4, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 1, 19, 0)), (unixTime(2017, OCTOBER, 1, 8, 0))); - assertEquals(applyTimezone(unixTime(2017, OCTOBER, 2, 19, 0)), (unixTime(2017, OCTOBER, 2, 8, 0))); - assertEquals(applyTimezone(unixTime(2017, NOVEMBER, 30, 19, 0)), (unixTime(2017, NOVEMBER, 30, 8, 0))); - assertEquals(applyTimezone(unixTime(2018, MARCH, 31, 0, 0)), (unixTime(2018, MARCH, 30, 13, 0))); - assertEquals(applyTimezone(unixTime(2018, MARCH, 31, 12, 0)), (unixTime(2018, MARCH, 31, 1, 0))); - assertEquals(applyTimezone(unixTime(2018, MARCH, 31, 18, 0)), (unixTime(2018, MARCH, 31, 7, 0))); - assertEquals(applyTimezone(unixTime(2018, APRIL, 1, 0, 0)), (unixTime(2018, MARCH, 31, 13, 0))); - assertEquals(applyTimezone(unixTime(2018, APRIL, 1, 1, 0)), (unixTime(2018, MARCH, 31, 14, 0))); - assertEquals(applyTimezone(unixTime(2018, APRIL, 1, 1, 59)), (unixTime(2018, MARCH, 31, 14, 59))); - // DST ends - assertEquals(applyTimezone(unixTime(2018, APRIL, 1, 2, 0)), (unixTime(2018, MARCH, 31, 16, 0))); - assertEquals(applyTimezone(unixTime(2018, APRIL, 1, 3, 0)), (unixTime(2018, MARCH, 31, 17, 0))); - assertEquals(applyTimezone(unixTime(2018, APRIL, 1, 4, 0)), (unixTime(2018, MARCH, 31, 18, 0))); - assertEquals(applyTimezone(unixTime(2018, APRIL, 1, 10, 0)), (unixTime(2018, APRIL, 1, 0, 0))); - assertEquals(applyTimezone(unixTime(2018, APRIL, 1, 18, 0)), (unixTime(2018, APRIL, 1, 8, 0))); - } - - @Test - public void test_removeTimezone() - { - DateUtils.setFixedTimeZone(TimeZone.getTimeZone("Australia/Sydney")); - assertEquals(removeTimezone(unixTime(2017, JULY, 30, 8, 0)), (unixTime(2017, JULY, 30, 18, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 29, 14, 0)), (unixTime(2017, SEPTEMBER, 30, 0, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 0, 0)), (unixTime(2017, SEPTEMBER, 30, 10, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 1, 0)), (unixTime(2017, SEPTEMBER, 30, 11, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 2, 0)), (unixTime(2017, SEPTEMBER, 30, 12, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 3, 0)), (unixTime(2017, SEPTEMBER, 30, 13, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 12, 0)), (unixTime(2017, SEPTEMBER, 30, 22, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 13, 0)), (unixTime(2017, SEPTEMBER, 30, 23, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 14, 0)), (unixTime(2017, OCTOBER, 1, 0, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 15, 0)), (unixTime(2017, OCTOBER, 1, 1, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 15, 59)), (unixTime(2017, OCTOBER, 1, 1, 59))); - // DST begins - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 16, 0)), (unixTime(2017, OCTOBER, 1, 3, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 17, 0)), (unixTime(2017, OCTOBER, 1, 4, 0))); - assertEquals(removeTimezone(unixTime(2017, SEPTEMBER, 30, 18, 0)), (unixTime(2017, OCTOBER, 1, 5, 0))); - assertEquals(removeTimezone(unixTime(2017, OCTOBER, 1, 0, 0)), (unixTime(2017, OCTOBER, 1, 11, 0))); - assertEquals(removeTimezone(unixTime(2017, OCTOBER, 1, 1, 0)), (unixTime(2017, OCTOBER, 1, 12, 0))); - assertEquals(removeTimezone(unixTime(2017, OCTOBER, 1, 2, 0)), (unixTime(2017, OCTOBER, 1, 13, 0))); - assertEquals(removeTimezone(unixTime(2017, OCTOBER, 1, 3, 0)), (unixTime(2017, OCTOBER, 1, 14, 0))); - assertEquals(removeTimezone(unixTime(2017, OCTOBER, 1, 4, 0)), (unixTime(2017, OCTOBER, 1, 15, 0))); - assertEquals(removeTimezone(unixTime(2017, OCTOBER, 1, 8, 0)), (unixTime(2017, OCTOBER, 1, 19, 0))); - assertEquals(removeTimezone(unixTime(2017, OCTOBER, 2, 8, 0)), (unixTime(2017, OCTOBER, 2, 19, 0))); - assertEquals(removeTimezone(unixTime(2017, NOVEMBER, 30, 8, 0)), (unixTime(2017, NOVEMBER, 30, 19, 0))); - assertEquals(removeTimezone(unixTime(2018, MARCH, 30, 13, 0)), (unixTime(2018, MARCH, 31, 0, 0))); - assertEquals(removeTimezone(unixTime(2018, MARCH, 31, 1, 0)), (unixTime(2018, MARCH, 31, 12, 0))); - assertEquals(removeTimezone(unixTime(2018, MARCH, 31, 7, 0)), (unixTime(2018, MARCH, 31, 18, 0))); - assertEquals(removeTimezone(unixTime(2018, MARCH, 31, 13, 0)), (unixTime(2018, APRIL, 1, 0, 0))); - assertEquals(removeTimezone(unixTime(2018, MARCH, 31, 14, 0)), (unixTime(2018, APRIL, 1, 1, 0))); - assertEquals(removeTimezone(unixTime(2018, MARCH, 31, 14, 59)), (unixTime(2018, APRIL, 1, 1, 59))); - // DST ends - assertEquals(removeTimezone(unixTime(2018, MARCH, 31, 16, 0)), (unixTime(2018, APRIL, 1, 2, 0))); - assertEquals(removeTimezone(unixTime(2018, MARCH, 31, 17, 0)), (unixTime(2018, APRIL, 1, 3, 0))); - assertEquals(removeTimezone(unixTime(2018, MARCH, 31, 18, 0)), (unixTime(2018, APRIL, 1, 4, 0))); - assertEquals(removeTimezone(unixTime(2018, APRIL, 1, 0, 0)), (unixTime(2018, APRIL, 1, 10, 0))); - assertEquals(removeTimezone(unixTime(2018, APRIL, 1, 8, 0)), (unixTime(2018, APRIL, 1, 18, 0))); - } -} diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt new file mode 100644 index 000000000..1b440279e --- /dev/null +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/utils/DateUtilsTest.kt @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2016-2021 Á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.core.utils + +import junit.framework.Assert.assertEquals +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.core.IsEqual.equalTo +import org.isoron.uhabits.core.BaseUnitTest +import org.isoron.uhabits.core.models.Timestamp +import org.isoron.uhabits.core.utils.DateUtils.Companion.applyTimezone +import org.isoron.uhabits.core.utils.DateUtils.Companion.formatHeaderDate +import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfDayWithOffset +import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset +import org.isoron.uhabits.core.utils.DateUtils.Companion.millisecondsUntilTomorrowWithOffset +import org.isoron.uhabits.core.utils.DateUtils.Companion.removeTimezone +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedLocalTime +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedLocale +import org.isoron.uhabits.core.utils.DateUtils.Companion.setFixedTimeZone +import org.isoron.uhabits.core.utils.DateUtils.Companion.setStartDayOffset +import org.isoron.uhabits.core.utils.DateUtils.Companion.truncate +import org.junit.Before +import org.junit.Test +import java.util.Calendar +import java.util.Locale +import java.util.TimeZone + +class DateUtilsTest : BaseUnitTest() { + var firstWeekday = Calendar.SUNDAY + + @Before + @Throws(Exception::class) + override fun setUp() { + super.setUp() + setFixedLocale(Locale.US) + } + + @Test + fun testFormatHeaderDate() { + val timestamp = unixTime(2015, Calendar.DECEMBER, 31) + val date = Timestamp(timestamp).toCalendar() + val formatted = formatHeaderDate(date) + assertThat(formatted, equalTo("Thu\n31")) + } + + @Test + fun testTruncate_dayOfWeek() { + val field = DateUtils.TruncateField.WEEK_NUMBER + var expected = unixTime(2015, Calendar.JANUARY, 11) + var t0 = unixTime(2015, Calendar.JANUARY, 11) + var t1 = unixTime(2015, Calendar.JANUARY, 16) + var t2 = unixTime(2015, Calendar.JANUARY, 17) + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + expected = unixTime(2015, Calendar.JANUARY, 18) + t0 = unixTime(2015, Calendar.JANUARY, 18) + t1 = unixTime(2015, Calendar.JANUARY, 19) + t2 = unixTime(2015, Calendar.JANUARY, 24) + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + firstWeekday = Calendar.WEDNESDAY + expected = unixTime(2015, Calendar.JANUARY, 7) + t0 = unixTime(2015, Calendar.JANUARY, 7) + t1 = unixTime(2015, Calendar.JANUARY, 9) + t2 = unixTime(2015, Calendar.JANUARY, 13) + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + } + + @Test + fun testTruncate_month() { + var expected = unixTime(2016, Calendar.JUNE, 1) + var t0 = unixTime(2016, Calendar.JUNE, 1) + var t1 = unixTime(2016, Calendar.JUNE, 15) + var t2 = unixTime(2016, Calendar.JUNE, 20) + val field = DateUtils.TruncateField.MONTH + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + expected = unixTime(2016, Calendar.DECEMBER, 1) + t0 = unixTime(2016, Calendar.DECEMBER, 1) + t1 = unixTime(2016, Calendar.DECEMBER, 15) + t2 = unixTime(2016, Calendar.DECEMBER, 31) + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + } + + @Test + fun testTruncate_quarter() { + val field = DateUtils.TruncateField.QUARTER + var expected = unixTime(2016, Calendar.JANUARY, 1) + var t0 = unixTime(2016, Calendar.JANUARY, 20) + var t1 = unixTime(2016, Calendar.FEBRUARY, 15) + var t2 = unixTime(2016, Calendar.MARCH, 30) + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + expected = unixTime(2016, Calendar.APRIL, 1) + t0 = unixTime(2016, Calendar.APRIL, 1) + t1 = unixTime(2016, Calendar.MAY, 30) + t2 = unixTime(2016, Calendar.JUNE, 20) + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + } + + @Test + fun testTruncate_year() { + val field = DateUtils.TruncateField.YEAR + var expected = unixTime(2016, Calendar.JANUARY, 1) + var t0 = unixTime(2016, Calendar.JANUARY, 1) + var t1 = unixTime(2016, Calendar.FEBRUARY, 25) + var t2 = unixTime(2016, Calendar.DECEMBER, 31) + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + expected = unixTime(2017, Calendar.JANUARY, 1) + t0 = unixTime(2017, Calendar.JANUARY, 1) + t1 = unixTime(2017, Calendar.MAY, 30) + t2 = unixTime(2017, Calendar.DECEMBER, 31) + assertThat(truncate(field, t0, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t1, firstWeekday), equalTo(expected)) + assertThat(truncate(field, t2, firstWeekday), equalTo(expected)) + } + + @Test + @Throws(Exception::class) + fun testMillisecondsUntilTomorrow() { + setFixedTimeZone(TimeZone.getTimeZone("GMT")) + setFixedLocalTime(unixTime(2017, Calendar.JANUARY, 1, 23, 59)) + assertThat(millisecondsUntilTomorrowWithOffset(), equalTo(DateUtils.MINUTE_LENGTH)) + setFixedLocalTime(unixTime(2017, Calendar.JANUARY, 1, 20, 0)) + assertThat( + millisecondsUntilTomorrowWithOffset(), + equalTo(4 * DateUtils.HOUR_LENGTH) + ) + setStartDayOffset(3, 30) + setFixedLocalTime(unixTime(2017, Calendar.JANUARY, 1, 23, 59)) + assertThat( + millisecondsUntilTomorrowWithOffset(), + equalTo(3 * DateUtils.HOUR_LENGTH + 31 * DateUtils.MINUTE_LENGTH) + ) + setFixedLocalTime(unixTime(2017, Calendar.JANUARY, 2, 1, 0)) + assertThat( + millisecondsUntilTomorrowWithOffset(), + equalTo(2 * DateUtils.HOUR_LENGTH + 30 * DateUtils.MINUTE_LENGTH) + ) + } + + @Test + @Throws(Exception::class) + fun testGetTodayWithOffset() { + assertThat(getTodayWithOffset(), equalTo(Timestamp(FIXED_LOCAL_TIME))) + setStartDayOffset(9, 0) + assertThat( + getTodayWithOffset(), + equalTo(Timestamp(FIXED_LOCAL_TIME - DateUtils.DAY_LENGTH)) + ) + } + + @Test + @Throws(Exception::class) + fun testGetStartOfDayWithOffset() { + val timestamp = unixTime(2020, Calendar.SEPTEMBER, 3) + assertThat( + getStartOfDayWithOffset(timestamp + DateUtils.HOUR_LENGTH), + equalTo(timestamp) + ) + setStartDayOffset(3, 30) + assertThat( + getStartOfDayWithOffset(timestamp + 3 * DateUtils.HOUR_LENGTH + 29 * DateUtils.MINUTE_LENGTH), + equalTo(timestamp - DateUtils.DAY_LENGTH) + ) + } + + @Test + fun test_applyTimezone() { + setFixedTimeZone(TimeZone.getTimeZone("Australia/Sydney")) + assertEquals( + applyTimezone(unixTime(2017, Calendar.JULY, 30, 18, 0)), + unixTime(2017, Calendar.JULY, 30, 8, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 0, 0)), + unixTime(2017, Calendar.SEPTEMBER, 29, 14, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 10, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 0, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 11, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 1, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 12, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 2, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 13, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 3, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 22, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 12, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 23, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 13, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 0, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 14, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 1, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 15, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 1, 59)), + unixTime(2017, Calendar.SEPTEMBER, 30, 15, 59) + ) + // DST begins + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 3, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 16, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 4, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 17, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 5, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 18, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 11, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 0, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 12, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 1, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 13, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 2, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 14, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 3, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 15, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 4, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 1, 19, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 8, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.OCTOBER, 2, 19, 0)), + unixTime(2017, Calendar.OCTOBER, 2, 8, 0) + ) + assertEquals( + applyTimezone(unixTime(2017, Calendar.NOVEMBER, 30, 19, 0)), + unixTime(2017, Calendar.NOVEMBER, 30, 8, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.MARCH, 31, 0, 0)), + unixTime(2018, Calendar.MARCH, 30, 13, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.MARCH, 31, 12, 0)), + unixTime(2018, Calendar.MARCH, 31, 1, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.MARCH, 31, 18, 0)), + unixTime(2018, Calendar.MARCH, 31, 7, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.APRIL, 1, 0, 0)), + unixTime(2018, Calendar.MARCH, 31, 13, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.APRIL, 1, 1, 0)), + unixTime(2018, Calendar.MARCH, 31, 14, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.APRIL, 1, 1, 59)), + unixTime(2018, Calendar.MARCH, 31, 14, 59) + ) + // DST ends + assertEquals( + applyTimezone(unixTime(2018, Calendar.APRIL, 1, 2, 0)), + unixTime(2018, Calendar.MARCH, 31, 16, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.APRIL, 1, 3, 0)), + unixTime(2018, Calendar.MARCH, 31, 17, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.APRIL, 1, 4, 0)), + unixTime(2018, Calendar.MARCH, 31, 18, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.APRIL, 1, 10, 0)), + unixTime(2018, Calendar.APRIL, 1, 0, 0) + ) + assertEquals( + applyTimezone(unixTime(2018, Calendar.APRIL, 1, 18, 0)), + unixTime(2018, Calendar.APRIL, 1, 8, 0) + ) + } + + @Test + fun test_removeTimezone() { + setFixedTimeZone(TimeZone.getTimeZone("Australia/Sydney")) + assertEquals( + removeTimezone(unixTime(2017, Calendar.JULY, 30, 8, 0)), + unixTime(2017, Calendar.JULY, 30, 18, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 29, 14, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 0, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 0, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 10, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 1, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 11, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 2, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 12, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 3, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 13, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 12, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 22, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 13, 0)), + unixTime(2017, Calendar.SEPTEMBER, 30, 23, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 14, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 0, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 15, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 1, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 15, 59)), + unixTime(2017, Calendar.OCTOBER, 1, 1, 59) + ) + // DST begins + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 16, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 3, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 17, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 4, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.SEPTEMBER, 30, 18, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 5, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 0, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 11, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 1, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 12, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 2, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 13, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 3, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 14, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 4, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 15, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.OCTOBER, 1, 8, 0)), + unixTime(2017, Calendar.OCTOBER, 1, 19, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.OCTOBER, 2, 8, 0)), + unixTime(2017, Calendar.OCTOBER, 2, 19, 0) + ) + assertEquals( + removeTimezone(unixTime(2017, Calendar.NOVEMBER, 30, 8, 0)), + unixTime(2017, Calendar.NOVEMBER, 30, 19, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 30, 13, 0)), + unixTime(2018, Calendar.MARCH, 31, 0, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 31, 1, 0)), + unixTime(2018, Calendar.MARCH, 31, 12, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 31, 7, 0)), + unixTime(2018, Calendar.MARCH, 31, 18, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 31, 13, 0)), + unixTime(2018, Calendar.APRIL, 1, 0, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 31, 14, 0)), + unixTime(2018, Calendar.APRIL, 1, 1, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 31, 14, 59)), + unixTime(2018, Calendar.APRIL, 1, 1, 59) + ) + // DST ends + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 31, 16, 0)), + unixTime(2018, Calendar.APRIL, 1, 2, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 31, 17, 0)), + unixTime(2018, Calendar.APRIL, 1, 3, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.MARCH, 31, 18, 0)), + unixTime(2018, Calendar.APRIL, 1, 4, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.APRIL, 1, 0, 0)), + unixTime(2018, Calendar.APRIL, 1, 10, 0) + ) + assertEquals( + removeTimezone(unixTime(2018, Calendar.APRIL, 1, 8, 0)), + unixTime(2018, Calendar.APRIL, 1, 18, 0) + ) + } +} diff --git a/uhabits-server/build.gradle b/uhabits-server/build.gradle index 9d9b99672..f108d6a21 100644 --- a/uhabits-server/build.gradle +++ b/uhabits-server/build.gradle @@ -65,7 +65,7 @@ dependencies { implementation "io.prometheus:simpleclient_httpserver:0.9.0" implementation "io.prometheus:simpleclient_hotspot:0.9.0" testImplementation "io.ktor:ktor-server-tests:$ktor_version" - testImplementation "org.mockito:mockito-core:2.+" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" } shadowJar { @@ -85,4 +85,4 @@ dockerRun { ports '8080:8080' daemonize false clean true -} \ No newline at end of file +} diff --git a/uhabits-server/gradle.properties b/uhabits-server/gradle.properties index f7235a299..b7300cbf9 100644 --- a/uhabits-server/gradle.properties +++ b/uhabits-server/gradle.properties @@ -19,5 +19,5 @@ ktor_version=1.4.1 kotlin.code.style=official -kotlin_version=1.4.10 +kotlin_version=1.4.21 logback_version=1.2.1 diff --git a/uhabits-server/src/org/isoron/uhabits/sync/links/Link.kt b/uhabits-server/src/org/isoron/uhabits/sync/links/Link.kt index b28f07207..9adf50614 100644 --- a/uhabits-server/src/org/isoron/uhabits/sync/links/Link.kt +++ b/uhabits-server/src/org/isoron/uhabits/sync/links/Link.kt @@ -19,8 +19,7 @@ package org.isoron.uhabits.sync.links -import org.isoron.uhabits.sync.* -import java.time.* +import org.isoron.uhabits.sync.defaultMapper /** * A Link maps a public URL (such as https://sync.loophabits.org/links/B752A6) diff --git a/uhabits-server/src/org/isoron/uhabits/sync/repository/Repository.kt b/uhabits-server/src/org/isoron/uhabits/sync/repository/Repository.kt index abee43e73..651ca679f 100644 --- a/uhabits-server/src/org/isoron/uhabits/sync/repository/Repository.kt +++ b/uhabits-server/src/org/isoron/uhabits/sync/repository/Repository.kt @@ -19,8 +19,8 @@ package org.isoron.uhabits.sync.repository -import com.sun.org.apache.xpath.internal.operations.* -import org.isoron.uhabits.sync.* +import org.isoron.uhabits.sync.KeyNotFoundException +import org.isoron.uhabits.sync.SyncData /** * A class that knows how to store and retrieve a large number of [SyncData] items. diff --git a/uhabits-server/test/org/isoron/uhabits/sync/app/LinksModuleTest.kt b/uhabits-server/test/org/isoron/uhabits/sync/app/LinksModuleTest.kt index d86ab9f42..ef2bc8963 100644 --- a/uhabits-server/test/org/isoron/uhabits/sync/app/LinksModuleTest.kt +++ b/uhabits-server/test/org/isoron/uhabits/sync/app/LinksModuleTest.kt @@ -19,14 +19,22 @@ package org.isoron.uhabits.sync.app -import io.ktor.http.* -import io.ktor.server.testing.* -import kotlinx.coroutines.* -import org.isoron.uhabits.sync.* -import org.isoron.uhabits.sync.links.* +import com.nhaarman.mockitokotlin2.whenever +import io.ktor.http.ContentType +import io.ktor.http.HttpHeaders +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.server.testing.TestApplicationCall +import io.ktor.server.testing.TestApplicationEngine +import io.ktor.server.testing.handleRequest +import io.ktor.server.testing.setBody +import io.ktor.server.testing.withTestApplication +import kotlinx.coroutines.runBlocking +import org.isoron.uhabits.sync.KeyNotFoundException +import org.isoron.uhabits.sync.links.Link +import org.isoron.uhabits.sync.links.toJson import org.junit.Test -import org.mockito.Mockito.* -import kotlin.test.* +import kotlin.test.assertEquals class LinksModuleTest : BaseApplicationTest() { private val link = Link( @@ -37,7 +45,7 @@ class LinksModuleTest : BaseApplicationTest() { @Test fun `when POST is successful should return link`(): Unit = runBlocking { - `when`(server.registerLink("SECRET")).thenReturn(link) + whenever(server.registerLink("SECRET")).thenReturn(link) withTestApplication(app()) { handlePost("/links", LinkRegisterRequestData(syncKey = "SECRET")).apply { assertEquals(HttpStatusCode.OK, response.status()) @@ -48,7 +56,7 @@ class LinksModuleTest : BaseApplicationTest() { @Test fun `when GET is successful should return link`(): Unit = runBlocking { - `when`(server.getLink("ABC123")).thenReturn(link) + whenever(server.getLink("ABC123")).thenReturn(link) withTestApplication(app()) { handleGet("/links/ABC123").apply { assertEquals(HttpStatusCode.OK, response.status()) @@ -59,7 +67,7 @@ class LinksModuleTest : BaseApplicationTest() { @Test fun `GET with invalid link id should return 404`(): Unit = runBlocking { - `when`(server.getLink("ABC123")).thenThrow(KeyNotFoundException()) + whenever(server.getLink("ABC123")).thenThrow(KeyNotFoundException()) withTestApplication(app()) { handleGet("/links/ABC123").apply { assertEquals(HttpStatusCode.NotFound, response.status()) @@ -67,7 +75,10 @@ class LinksModuleTest : BaseApplicationTest() { } } - private fun TestApplicationEngine.handlePost(url: String, data: LinkRegisterRequestData): TestApplicationCall { + private fun TestApplicationEngine.handlePost( + url: String, + data: LinkRegisterRequestData + ): TestApplicationCall { return handleRequest(HttpMethod.Post, url) { addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString()) setBody(data.toJson()) @@ -77,5 +88,4 @@ class LinksModuleTest : BaseApplicationTest() { private fun TestApplicationEngine.handleGet(url: String): TestApplicationCall { return handleRequest(HttpMethod.Get, url) } - -} \ No newline at end of file +} diff --git a/uhabits-server/test/org/isoron/uhabits/sync/app/RegistrationModuleTest.kt b/uhabits-server/test/org/isoron/uhabits/sync/app/RegistrationModuleTest.kt index dfb2bfa4c..8fa80883a 100644 --- a/uhabits-server/test/org/isoron/uhabits/sync/app/RegistrationModuleTest.kt +++ b/uhabits-server/test/org/isoron/uhabits/sync/app/RegistrationModuleTest.kt @@ -19,19 +19,20 @@ package org.isoron.uhabits.sync.app -import io.ktor.http.* -import io.ktor.server.testing.* -import kotlinx.coroutines.* -import org.isoron.uhabits.sync.* +import com.nhaarman.mockitokotlin2.whenever +import io.ktor.http.HttpMethod +import io.ktor.http.HttpStatusCode +import io.ktor.server.testing.handleRequest +import io.ktor.server.testing.withTestApplication +import kotlinx.coroutines.runBlocking +import org.isoron.uhabits.sync.ServiceUnavailable import org.junit.Test -import org.mockito.* -import org.mockito.Mockito.* -import kotlin.test.* +import kotlin.test.assertEquals class RegistrationModuleTest : BaseApplicationTest() { @Test - fun `when register succeeds should return generated key`():Unit = runBlocking { - `when`(server.register()).thenReturn("ABCDEF") + fun `when register succeeds should return generated key`(): Unit = runBlocking { + whenever(server.register()).thenReturn("ABCDEF") withTestApplication(app()) { val call = handleRequest(HttpMethod.Post, "/register") assertEquals(HttpStatusCode.OK, call.response.status()) @@ -40,11 +41,11 @@ class RegistrationModuleTest : BaseApplicationTest() { } @Test - fun `when registration is unavailable should return 503`():Unit = runBlocking { - `when`(server.register()).thenThrow(ServiceUnavailable()) + fun `when registration is unavailable should return 503`(): Unit = runBlocking { + whenever(server.register()).thenThrow(ServiceUnavailable()) withTestApplication(app()) { val call = handleRequest(HttpMethod.Post, "/register") assertEquals(HttpStatusCode.ServiceUnavailable, call.response.status()) } } -} \ No newline at end of file +} diff --git a/uhabits-server/test/org/isoron/uhabits/sync/app/StorageModuleTest.kt b/uhabits-server/test/org/isoron/uhabits/sync/app/StorageModuleTest.kt index 9ba4f653f..4165bab5e 100644 --- a/uhabits-server/test/org/isoron/uhabits/sync/app/StorageModuleTest.kt +++ b/uhabits-server/test/org/isoron/uhabits/sync/app/StorageModuleTest.kt @@ -19,6 +19,7 @@ package org.isoron.uhabits.sync.app +import com.nhaarman.mockitokotlin2.whenever import io.ktor.http.* import io.ktor.server.testing.* import kotlinx.coroutines.* @@ -33,7 +34,7 @@ class StorageModuleTest : BaseApplicationTest() { @Test fun `when get succeeds should return data`(): Unit = runBlocking { - `when`(server.getData("k1")).thenReturn(data1) + whenever(server.getData("k1")).thenReturn(data1) withTestApplication(app()) { handleGet("/db/k1").apply { assertEquals(HttpStatusCode.OK, response.status()) @@ -44,7 +45,7 @@ class StorageModuleTest : BaseApplicationTest() { @Test fun `when get version succeeds should return version`(): Unit = runBlocking { - `when`(server.getDataVersion("k1")).thenReturn(30) + whenever(server.getDataVersion("k1")).thenReturn(30) withTestApplication(app()) { handleGet("/db/k1/version").apply { assertEquals(HttpStatusCode.OK, response.status()) @@ -55,7 +56,7 @@ class StorageModuleTest : BaseApplicationTest() { @Test fun `when get with invalid key should return 404`(): Unit = runBlocking { - `when`(server.getData("k1")).thenThrow(KeyNotFoundException()) + whenever(server.getData("k1")).thenThrow(KeyNotFoundException()) withTestApplication(app()) { handleGet("/db/k1").apply { assertEquals(HttpStatusCode.NotFound, response.status()) @@ -78,7 +79,7 @@ class StorageModuleTest : BaseApplicationTest() { @Test fun `when put with invalid key should return 404`(): Unit = runBlocking { - `when`(server.put("k1", data1)).thenThrow(KeyNotFoundException()) + whenever(server.put("k1", data1)).thenThrow(KeyNotFoundException()) withTestApplication(app()) { handlePut("/db/k1", data1).apply { assertEquals(HttpStatusCode.NotFound, response.status()) @@ -88,8 +89,8 @@ class StorageModuleTest : BaseApplicationTest() { @Test fun `when put with invalid version should return 409 and current data`(): Unit = runBlocking { - `when`(server.put("k1", data1)).thenThrow(EditConflictException()) - `when`(server.getData("k1")).thenReturn(data2) + whenever(server.put("k1", data1)).thenThrow(EditConflictException()) + whenever(server.getData("k1")).thenReturn(data2) withTestApplication(app()) { handlePut("/db/k1", data1).apply { assertEquals(HttpStatusCode.Conflict, response.status())