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