diff --git a/build.gradle.kts b/build.gradle.kts
index 993a59a6c..102ed1278 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,11 +1,11 @@
plugins {
val kotlinVersion = "1.5.0"
- id("com.android.application") version ("7.0.2") apply (false)
+ id("com.android.application") version ("7.0.3") apply (false)
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.android.extensions") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
- id("org.jlleitschuh.gradle.ktlint") version "10.1.0"
+ id("org.jlleitschuh.gradle.ktlint") version "10.2.1"
}
apply {
@@ -18,7 +18,6 @@ allprojects {
mavenCentral()
maven(url = "https://plugins.gradle.org/m2/")
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
- maven(url = "https://kotlin.bintray.com/ktor")
- maven(url = "https://kotlin.bintray.com/kotlin-js-wrappers")
+ maven(url = "https://jitpack.io")
}
}
diff --git a/gradle.properties b/gradle.properties
index a1f977f3b..b54c6c097 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
org.gradle.parallel=false
org.gradle.daemon=true
-org.gradle.jvmargs=-Xms2048m -Xmx2048m -XX:MaxPermSize=2048m
+org.gradle.jvmargs=-Xms2048m -Xmx2048m
android.useAndroidX=true
android.enableJetifier=true
diff --git a/uhabits-android/build.gradle.kts b/uhabits-android/build.gradle.kts
index 319a19f2f..b4ef2d919 100644
--- a/uhabits-android/build.gradle.kts
+++ b/uhabits-android/build.gradle.kts
@@ -18,7 +18,7 @@
*/
plugins {
- id("com.github.triplet.play") version "3.6.0"
+ id("com.github.triplet.play") version "3.7.0"
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.kapt")
@@ -32,13 +32,13 @@ tasks.compileLint {
android {
- compileSdk = 30
+ compileSdk = 31
defaultConfig {
versionCode = 20003
versionName = "2.0.3"
minSdk = 23
- targetSdk = 30
+ targetSdk = 31
applicationId = "org.isoron.uhabits"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -86,10 +86,10 @@ android {
}
dependencies {
- val daggerVersion = "2.38.1"
- val kotlinVersion = "1.5.30"
- val kxCoroutinesVersion = "1.5.1"
- val ktorVersion = "1.6.3"
+ val daggerVersion = "2.41"
+ val kotlinVersion = "1.6.10"
+ val kxCoroutinesVersion = "1.6.0"
+ val ktorVersion = "1.6.7"
val espressoVersion = "3.4.0"
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
@@ -98,17 +98,17 @@ dependencies {
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.1")
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
- androidTestImplementation("androidx.annotation:annotation:1.2.0")
+ androidTestImplementation("androidx.annotation:annotation:1.3.0")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
androidTestImplementation("androidx.test:rules:1.4.0")
androidTestImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
compileOnly("javax.annotation:jsr250-api:1.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
- implementation("com.github.paolorotolo:appintro:4.1.0")
+ implementation("com.github.AppIntro:AppIntro:6.2.0")
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.google.dagger:dagger:$daggerVersion")
- implementation("com.google.guava:guava:30.1.1-android")
+ implementation("com.google.guava:guava:31.1-android")
implementation("io.ktor:ktor-client-android:$ktorVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
@@ -116,11 +116,11 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kxCoroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kxCoroutinesVersion")
- implementation("androidx.appcompat:appcompat:1.3.1")
+ implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.legacy:legacy-preference-v14:1.0.0")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
- implementation("com.google.android.material:material:1.4.0")
- implementation("com.opencsv:opencsv:5.5.1")
+ implementation("com.google.android.material:material:1.5.0")
+ implementation("com.opencsv:opencsv:5.6")
implementation(project(":uhabits-core"))
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/CheckmarkButtonView/render_implicit_check.png b/uhabits-android/src/androidTest/assets/views/habits/list/CheckmarkButtonView/render_implicit_check.png
index 86e6a5406..c45314e03 100644
Binary files a/uhabits-android/src/androidTest/assets/views/habits/list/CheckmarkButtonView/render_implicit_check.png and b/uhabits-android/src/androidTest/assets/views/habits/list/CheckmarkButtonView/render_implicit_check.png differ
diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_above.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_above.png
new file mode 100644
index 000000000..4cea80498
Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_above.png differ
diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_below.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_below.png
new file mode 100644
index 000000000..ac4c0f5c9
Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_below.png differ
diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_between.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_between.png
new file mode 100644
index 000000000..12063c425
Binary files /dev/null and b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_at_most_between.png differ
diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_zero.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_zero.png
index 5f6bf8c65..a405c0092 100644
Binary files a/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_zero.png and b/uhabits-android/src/androidTest/assets/views/habits/list/NumberButtonView/render_zero.png differ
diff --git a/uhabits-android/src/androidTest/assets/views/habits/list/NumberPanelView/render.png b/uhabits-android/src/androidTest/assets/views/habits/list/NumberPanelView/render.png
index 22584afb3..a8571c3fa 100644
Binary files a/uhabits-android/src/androidTest/assets/views/habits/list/NumberPanelView/render.png and b/uhabits-android/src/androidTest/assets/views/habits/list/NumberPanelView/render.png differ
diff --git a/uhabits-android/src/androidTest/assets/views/habits/show/NotesCard/render.png b/uhabits-android/src/androidTest/assets/views/habits/show/NotesCard/render.png
index dafb53f4f..f1cfd0676 100644
Binary files a/uhabits-android/src/androidTest/assets/views/habits/show/NotesCard/render.png and b/uhabits-android/src/androidTest/assets/views/habits/show/NotesCard/render.png differ
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
index 171a9ed41..bfbdf3bb8 100644
--- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/HabitsTest.kt
+++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/acceptance/HabitsTest.kt
@@ -55,6 +55,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
class HabitsTest : BaseUserInterfaceTest() {
+
@Test
@Throws(Exception::class)
fun shouldCreateHabit() {
@@ -180,6 +181,8 @@ class HabitsTest : BaseUserInterfaceTest() {
longPressCheckmarks("Wake up early", count = 2)
clickText("Wake up early")
verifyShowsScreen(SHOW_HABIT)
+ // TODO: find a better way than sleeping in tests
+ Thread.sleep(2001L)
verifyDisplaysText("10%")
}
@@ -194,6 +197,8 @@ class HabitsTest : BaseUserInterfaceTest() {
verifyDoesNotDisplayText("Track time")
verifyDisplaysText("Wake up early")
longPressCheckmarks("Wake up early", count = 1)
+ // TODO: find a better way than sleeping in tests
+ Thread.sleep(2001L)
verifyDoesNotDisplayText("Wake up early")
clickMenu(TOGGLE_COMPLETED)
verifyDisplaysText("Track time")
diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt
index 6f1923aaa..935fc7028 100644
--- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt
+++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonViewTest.kt
@@ -24,6 +24,7 @@ import androidx.test.filters.MediumTest
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.BaseViewTest
+import org.isoron.uhabits.core.models.NumericalHabitType
import org.isoron.uhabits.utils.PaletteUtils
import org.junit.Before
import org.junit.Test
@@ -42,6 +43,7 @@ class NumberButtonViewTest : BaseViewTest() {
super.setUp()
view = component.getNumberButtonViewFactory().create().apply {
units = "steps"
+ targetType = NumericalHabitType.AT_LEAST
threshold = 100.0
color = PaletteUtils.getAndroidTestColor(8)
onEdit = { edited = true }
@@ -74,10 +76,10 @@ class NumberButtonViewTest : BaseViewTest() {
}
@Test
- fun testRender_emptyUnits() {
+ fun testRender_atMostAboveThreshold() {
view.value = 500.0
- view.units = ""
- assertRenders(view, "$PATH/render_unitless.png")
+ view.targetType = NumericalHabitType.AT_MOST
+ assertRenders(view, "$PATH/render_at_most_above.png")
}
@Test
@@ -86,6 +88,13 @@ class NumberButtonViewTest : BaseViewTest() {
assertRenders(view, "$PATH/render_below.png")
}
+ @Test
+ fun testRender_atMostBetweenThresholds() {
+ view.value = 110.0
+ view.targetType = NumericalHabitType.AT_MOST
+ assertRenders(view, "$PATH/render_at_most_between.png")
+ }
+
@Test
fun testRender_zero() {
view.value = 0.0
@@ -93,15 +102,21 @@ class NumberButtonViewTest : BaseViewTest() {
}
@Test
- fun testClick_shortToggleDisabled() {
- prefs.isShortToggleEnabled = false
- view.performClick()
- assertFalse(edited)
+ fun testRender_atMostBelowThreshold() {
+ view.value = 0.0
+ view.targetType = NumericalHabitType.AT_MOST
+ assertRenders(view, "$PATH/render_at_most_below.png")
+ }
+
+ @Test
+ fun testRender_emptyUnits() {
+ view.value = 500.0
+ view.units = ""
+ assertRenders(view, "$PATH/render_unitless.png")
}
@Test
- fun testClick_shortToggleEnabled() {
- prefs.isShortToggleEnabled = true
+ fun testClick() {
view.performClick()
assertTrue(edited)
}
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 bd046acc7..da0a2eb8a 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
@@ -24,6 +24,7 @@ import androidx.test.filters.MediumTest
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.BaseViewTest
+import org.isoron.uhabits.core.models.NumericalHabitType
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.utils.PaletteUtils
import org.junit.After
@@ -55,6 +56,7 @@ class NumberPanelViewTest : BaseViewTest() {
buttonCount = 4
color = PaletteUtils.getAndroidTestColor(7)
units = "steps"
+ targetType = NumericalHabitType.AT_LEAST
threshold = 5000.0
}
view.onAttachedToWindow()
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 a43543936..3323f78df 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
@@ -53,8 +53,6 @@ class SubtitleCardViewTest : BaseViewTest() {
isNumerical = false,
question = "Did you meditate this morning?",
reminder = Reminder(8, 30, EVERY_DAY),
- unit = "",
- targetValue = 0.0,
theme = LightTheme(),
)
)
diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/performance/PerformanceTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/performance/PerformanceTest.kt
index 10ad56057..d2debb851 100644
--- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/performance/PerformanceTest.kt
+++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/performance/PerformanceTest.kt
@@ -61,7 +61,7 @@ class PerformanceTest : BaseAndroidTest() {
val habit = fixtures.createEmptyHabit()
for (i in 0..4999) {
val timestamp: Timestamp = Timestamp(i * DAY_LENGTH)
- CreateRepetitionCommand(habitList, habit, timestamp, 1).run()
+ CreateRepetitionCommand(habitList, habit, timestamp, 1, "").run()
}
db.setTransactionSuccessful()
db.endTransaction()
diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/CheckmarkWidgetTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/CheckmarkWidgetTest.kt
index b73c49c1b..05adfdbbe 100644
--- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/CheckmarkWidgetTest.kt
+++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/CheckmarkWidgetTest.kt
@@ -24,8 +24,8 @@ import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.hamcrest.CoreMatchers
-import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
diff --git a/uhabits-android/src/main/AndroidManifest.xml b/uhabits-android/src/main/AndroidManifest.xml
index b5d06f181..3b0851207 100644
--- a/uhabits-android/src/main/AndroidManifest.xml
+++ b/uhabits-android/src/main/AndroidManifest.xml
@@ -17,9 +17,10 @@
~ with this program. If not, see .
-->
+
+
@@ -48,11 +49,11 @@
android:name=".activities.habits.list.ListHabitsActivity"
android:exported="true"
android:label="@string/main_activity_title"
- android:launchMode="singleTop">
-
+ android:launchMode="singleTop" />
@@ -85,6 +86,7 @@
@@ -93,6 +95,7 @@
@@ -101,6 +104,7 @@
@@ -117,9 +121,10 @@
@@ -128,13 +133,14 @@
@@ -152,6 +158,7 @@
@@ -164,6 +171,7 @@
@@ -176,6 +184,7 @@
@@ -188,6 +197,7 @@
@@ -200,6 +210,7 @@
@@ -210,13 +221,17 @@
android:resource="@xml/widget_target_info" />
-
+
-
+
@@ -267,7 +282,7 @@
+ android:exported="false">
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 df5d5e184..0a8c5ee68 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
@@ -49,23 +49,12 @@ class AndroidDataView(
override fun onShowPress(e: MotionEvent?) = Unit
override fun onSingleTapUp(e: MotionEvent?): Boolean {
- val x: Float
- val y: Float
- try {
- val pointerId = e!!.getPointerId(0)
- x = e.getX(pointerId)
- y = e.getY(pointerId)
- } catch (ex: RuntimeException) {
- // Android often throws IllegalArgumentException here. Apparently,
- // the pointer id may become invalid shortly after calling
- // e.getPointerId.
- return false
- }
- view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
- return true
+ return handleClick(e, true)
}
- override fun onLongPress(e: MotionEvent?) = Unit
+ override fun onLongPress(e: MotionEvent?) {
+ handleClick(e)
+ }
override fun onScroll(
e1: MotionEvent?,
@@ -137,4 +126,22 @@ class AndroidDataView(
}
}
}
+
+ private fun handleClick(e: MotionEvent?, isSingleTap: Boolean = false): Boolean {
+ val x: Float
+ val y: Float
+ try {
+ val pointerId = e!!.getPointerId(0)
+ x = e.getX(pointerId)
+ y = e.getY(pointerId)
+ } catch (ex: RuntimeException) {
+ // Android often throws IllegalArgumentException here. Apparently,
+ // the pointer id may become invalid shortly after calling
+ // e.getPointerId.
+ return false
+ }
+ if (isSingleTap) view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
+ else view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
+ return true
+ }
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt
new file mode 100644
index 000000000..4e62b614b
--- /dev/null
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkDialog.kt
@@ -0,0 +1,119 @@
+package org.isoron.uhabits.activities.common.dialogs
+
+import android.content.Context
+import android.graphics.Typeface
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
+import android.widget.Button
+import androidx.appcompat.app.AlertDialog
+import org.isoron.platform.gui.toInt
+import org.isoron.platform.time.JavaLocalDateFormatter
+import org.isoron.platform.time.LocalDate
+import org.isoron.uhabits.R
+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.models.PaletteColor
+import org.isoron.uhabits.core.preferences.Preferences
+import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
+import org.isoron.uhabits.core.ui.views.Theme
+import org.isoron.uhabits.databinding.CheckmarkDialogBinding
+import org.isoron.uhabits.inject.ActivityContext
+import org.isoron.uhabits.utils.InterfaceUtils
+import org.isoron.uhabits.utils.StyledResources
+import java.util.Locale
+import javax.inject.Inject
+
+class CheckmarkDialog
+@Inject constructor(
+ @ActivityContext private val context: Context,
+ private val preferences: Preferences,
+) : View.OnClickListener {
+
+ private lateinit var binding: CheckmarkDialogBinding
+ private lateinit var fontAwesome: Typeface
+ private val allButtons = mutableListOf