From 804030f5c0d9582470a6bef82ebfcc9ec832918f Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 24 Jun 2025 22:10:36 -0500 Subject: [PATCH 01/17] EmptyListViewTest: Instantiate view within each test method --- .../activities/habits/list/views/EmptyListViewTest.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt index ac133ec7d..5ffa24f8b 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt @@ -35,23 +35,20 @@ class EmptyListViewTest : BaseViewTest() { } private val path = "habits/list/EmptyListView" - private val view: EmptyListView = EmptyListView(targetContext) - - @Before - override fun setUp() { - super.setUp() - measureView(view, dpToPixels(200), dpToPixels(200)) - } @Test fun testRender_done() { + val view = EmptyListView(targetContext) view.showDone() + measureView(view, dpToPixels(200), dpToPixels(200)) assertRenders(view, "$path/done.png") } @Test fun testRender_empty() { + val view = EmptyListView(targetContext) view.showEmpty() + measureView(view, dpToPixels(200), dpToPixels(200)) assertRenders(view, "$path/empty.png") } } From 862a851e1c4e3ca9edbe90652378e6d6a3f4c8b2 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 24 Jun 2025 22:13:08 -0500 Subject: [PATCH 02/17] EmptyListViewTest: Remove unused imports --- .../uhabits/activities/habits/list/views/EmptyListViewTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt index 5ffa24f8b..b0f19aa31 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt @@ -22,7 +22,6 @@ package org.isoron.uhabits.activities.habits.list.views import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import org.isoron.uhabits.BaseViewTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith From 97b98a872d765801cdc47d94ec1b432357672604 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 24 Jun 2025 22:22:14 -0500 Subject: [PATCH 03/17] EmptyListViewTest: Ignore non-deterministic test failures --- .../uhabits/activities/habits/list/views/EmptyListViewTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt index b0f19aa31..a37d85234 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/EmptyListViewTest.kt @@ -22,6 +22,7 @@ package org.isoron.uhabits.activities.habits.list.views import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import org.isoron.uhabits.BaseViewTest +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @@ -36,6 +37,7 @@ class EmptyListViewTest : BaseViewTest() { private val path = "habits/list/EmptyListView" @Test + @Ignore("non-deterministic failure") fun testRender_done() { val view = EmptyListView(targetContext) view.showDone() @@ -44,6 +46,7 @@ class EmptyListViewTest : BaseViewTest() { } @Test + @Ignore("non-deterministic failure") fun testRender_empty() { val view = EmptyListView(targetContext) view.showEmpty() From 5006f5128b856f447783936879c8bf8b5ad755b5 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 25 Jun 2025 21:00:24 -0500 Subject: [PATCH 04/17] Update JVM target and toolchain to version 17 --- uhabits-android/build.gradle.kts | 8 ++++---- uhabits-core/build.gradle.kts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/uhabits-android/build.gradle.kts b/uhabits-android/build.gradle.kts index 804b30c8f..bffb150bb 100644 --- a/uhabits-android/build.gradle.kts +++ b/uhabits-android/build.gradle.kts @@ -36,7 +36,7 @@ Updating gradle might fix this, so try again in the future to remove this and ru If this doesn't produce any warning, try to remove it. */ kotlin { - jvmToolchain(11) + jvmToolchain(17) } android { @@ -79,11 +79,11 @@ android { compileOptions { isCoreLibraryDesugaringEnabled = true - targetCompatibility(JavaVersion.VERSION_11) - sourceCompatibility(JavaVersion.VERSION_11) + targetCompatibility(JavaVersion.VERSION_17) + sourceCompatibility(JavaVersion.VERSION_17) } - kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString() + kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() buildFeatures.viewBinding = true lint.abortOnError = false } diff --git a/uhabits-core/build.gradle.kts b/uhabits-core/build.gradle.kts index 0baa70d72..aa0677bee 100644 --- a/uhabits-core/build.gradle.kts +++ b/uhabits-core/build.gradle.kts @@ -24,7 +24,7 @@ plugins { kotlin { jvm().withJava() - jvmToolchain(11) + jvmToolchain(17) sourceSets { val commonMain by getting { From 648c7277cfd4739a573bae8b4598823afdc372e1 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 25 Jun 2025 21:48:31 -0500 Subject: [PATCH 05/17] CSV export: Add more fields to Habits.csv --- .../assets/test/csv_export/Habits.csv | 6 +++--- .../isoron/uhabits/core/models/HabitList.kt | 21 ++++++++++++++----- .../uhabits/core/io/HabitsCSVExporterTest.kt | 19 +++++++---------- .../uhabits/core/models/HabitListTest.kt | 9 +++++--- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/uhabits-core/assets/test/csv_export/Habits.csv b/uhabits-core/assets/test/csv_export/Habits.csv index 672189f33..14943fa5c 100644 --- a/uhabits-core/assets/test/csv_export/Habits.csv +++ b/uhabits-core/assets/test/csv_export/Habits.csv @@ -1,3 +1,3 @@ -Position,Name,Question,Description,NumRepetitions,Interval,Color -001,Meditate,Did you meditate this morning?,,1,1,#FF8F00 -002,Wake up early,Did you wake up before 6am?,,2,3,#00897B +Position,Name,Type,Question,Description,FrequencyNumerator,FrequencyDenominator,Color,Unit,Target Type,Target Value,Archived? +001,Meditate,YES_NO,Did you meditate this morning?,,1,1,#FF8F00,,,,false +002,Wake up early,YES_NO,Did you wake up before 6am?,,2,3,#00897B,,,,false diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt index 74ee0a700..6227df991 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt @@ -22,6 +22,7 @@ import com.opencsv.CSVWriter import java.io.IOException import java.io.Writer import java.util.LinkedList +import java.util.Locale import javax.annotation.concurrent.ThreadSafe /** @@ -182,24 +183,34 @@ abstract class HabitList : Iterable { val header = arrayOf( "Position", "Name", + "Type", "Question", "Description", - "NumRepetitions", - "Interval", - "Color" + "FrequencyNumerator", + "FrequencyDenominator", + "Color", + "Unit", + "Target Type", + "Target Value", + "Archived?", ) val csv = CSVWriter(out) csv.writeNext(header, false) for (habit in this) { val (numerator, denominator) = habit.frequency val cols = arrayOf( - String.format("%03d", indexOf(habit) + 1), + String.format(Locale.US, "%03d", indexOf(habit) + 1), habit.name, + habit.type.name, habit.question, habit.description, numerator.toString(), denominator.toString(), - habit.color.toCsvColor() + habit.color.toCsvColor(), + if (habit.isNumerical) habit.unit else "", + if (habit.isNumerical) habit.targetType.name else "", + if (habit.isNumerical) habit.targetValue.toString() else "", + habit.isArchived.toString(), ) csv.writeNext(cols, false) } 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 index bbe069be8..5362e640e 100644 --- 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 @@ -18,7 +18,6 @@ */ package org.isoron.uhabits.core.io -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 @@ -30,6 +29,7 @@ import java.io.IOException import java.nio.file.Files import java.util.* import java.util.zip.ZipFile +import kotlin.test.assertEquals import kotlin.test.assertTrue class HabitsCSVExporterTest : BaseUnitTest() { @@ -108,15 +108,12 @@ class HabitsCSVExporterTest : BaseUnitTest() { private fun assertFileAndReferenceAreEqual(s: String) { val assetFilename = String.format("csv_export/%s", s) - val file = File.createTempFile("asset", "") - file.deleteOnExit() - copyAssetToFile(assetFilename, file) - - assertTrue( - FileUtils.contentEquals( - file, - File(String.format("%s/%s", baseDir.absolutePath, s)) - ) - ) + val expectedFile = File(String.format("%s/%s", baseDir.absolutePath, s)) + val actualFile = File.createTempFile("asset", "") + actualFile.deleteOnExit() + copyAssetToFile(assetFilename, actualFile) + val expectedContents = expectedFile.readText() + val actualContents = actualFile.readText() + assertEquals(expectedContents, actualContents, "content mismatch for $s") } } 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 index f4cecda6f..8272ef4e2 100644 --- 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 @@ -202,13 +202,16 @@ class HabitListTest : BaseUnitTest() { h2.description = "" h2.frequency = Frequency(2, 3) h2.color = PaletteColor(5) + val h3 = fixtures.createNumericalHabit() list.add(h1) list.add(h2) + list.add(h3) 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 + Position,Name,Type,Question,Description,FrequencyNumerator,FrequencyDenominator,Color,Unit,Target Type,Target Value,Archived? + 001,Meditate,YES_NO,Did you meditate this morning?,this is a test description,1,1,#FF8F00,,,,false + 002,Run,NUMERICAL,How many miles did you run today?,,1,1,#E64A19,miles,AT_LEAST,2.0,false + 003,Wake up early,YES_NO,Did you wake up before 6am?,,2,3,#AFB42B,,,,false """.trimIndent() val writer = StringWriter() From 035b392ece8fa80c941996a016a9461e4fe1f9de Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 25 Jun 2025 22:12:02 -0500 Subject: [PATCH 06/17] CSV export: Use formatted values, add notes & header --- .../csv_export/001 Meditate/Checkmarks.csv | 1 + .../test/csv_export/001 Meditate/Scores.csv | 1 + .../002 Wake up early/Checkmarks.csv | 21 ++++++++-------- .../csv_export/002 Wake up early/Scores.csv | 1 + .../assets/test/csv_export/Checkmarks.csv | 20 +++++++-------- .../uhabits/core/io/HabitsCSVExporter.kt | 25 ++++++++++++++----- .../org/isoron/uhabits/core/models/Entry.kt | 10 ++++++++ .../isoron/uhabits/core/test/HabitFixtures.kt | 10 +++++--- .../uhabits/core/io/HabitsCSVExporterTest.kt | 10 ++++---- 9 files changed, 65 insertions(+), 34 deletions(-) diff --git a/uhabits-core/assets/test/csv_export/001 Meditate/Checkmarks.csv b/uhabits-core/assets/test/csv_export/001 Meditate/Checkmarks.csv index e69de29bb..6033c92f7 100644 --- a/uhabits-core/assets/test/csv_export/001 Meditate/Checkmarks.csv +++ b/uhabits-core/assets/test/csv_export/001 Meditate/Checkmarks.csv @@ -0,0 +1 @@ +Date,Value,Notes diff --git a/uhabits-core/assets/test/csv_export/001 Meditate/Scores.csv b/uhabits-core/assets/test/csv_export/001 Meditate/Scores.csv index 6004f60bc..8d3f97177 100644 --- a/uhabits-core/assets/test/csv_export/001 Meditate/Scores.csv +++ b/uhabits-core/assets/test/csv_export/001 Meditate/Scores.csv @@ -1 +1,2 @@ +Date,Score 2015-01-25,0.0000 diff --git a/uhabits-core/assets/test/csv_export/002 Wake up early/Checkmarks.csv b/uhabits-core/assets/test/csv_export/002 Wake up early/Checkmarks.csv index 89f788f68..7b3da3855 100644 --- a/uhabits-core/assets/test/csv_export/002 Wake up early/Checkmarks.csv +++ b/uhabits-core/assets/test/csv_export/002 Wake up early/Checkmarks.csv @@ -1,10 +1,11 @@ -2015-01-25,2 -2015-01-24,0 -2015-01-23,1 -2015-01-22,2 -2015-01-21,2 -2015-01-20,2 -2015-01-19,1 -2015-01-18,1 -2015-01-17,2 -2015-01-16,2 +Date,Value,Notes +2015-01-25,YES_MANUAL, +2015-01-24,NO,Sick +2015-01-23,YES_AUTO,"Forgot to do it, really" +2015-01-22,YES_MANUAL, +2015-01-21,YES_MANUAL, +2015-01-20,YES_MANUAL, +2015-01-19,YES_AUTO,"""Vacation""" +2015-01-18,YES_AUTO, +2015-01-17,YES_MANUAL, +2015-01-16,YES_MANUAL, diff --git a/uhabits-core/assets/test/csv_export/002 Wake up early/Scores.csv b/uhabits-core/assets/test/csv_export/002 Wake up early/Scores.csv index 7f4fa4780..fb91385ff 100644 --- a/uhabits-core/assets/test/csv_export/002 Wake up early/Scores.csv +++ b/uhabits-core/assets/test/csv_export/002 Wake up early/Scores.csv @@ -1,3 +1,4 @@ +Date,Score 2015-01-25,0.2557 2015-01-24,0.2226 2015-01-23,0.1991 diff --git a/uhabits-core/assets/test/csv_export/Checkmarks.csv b/uhabits-core/assets/test/csv_export/Checkmarks.csv index c0788570b..f03bece31 100644 --- a/uhabits-core/assets/test/csv_export/Checkmarks.csv +++ b/uhabits-core/assets/test/csv_export/Checkmarks.csv @@ -1,11 +1,11 @@ Date,Meditate,Wake up early, -2015-01-25,-1,2, -2015-01-24,-1,0, -2015-01-23,-1,1, -2015-01-22,-1,2, -2015-01-21,-1,2, -2015-01-20,-1,2, -2015-01-19,-1,1, -2015-01-18,-1,1, -2015-01-17,-1,2, -2015-01-16,-1,2, +2015-01-25,UNKNOWN,YES_MANUAL, +2015-01-24,UNKNOWN,NO, +2015-01-23,UNKNOWN,YES_AUTO, +2015-01-22,UNKNOWN,YES_MANUAL, +2015-01-21,UNKNOWN,YES_MANUAL, +2015-01-20,UNKNOWN,YES_MANUAL, +2015-01-19,UNKNOWN,YES_AUTO, +2015-01-18,UNKNOWN,YES_AUTO, +2015-01-17,UNKNOWN,YES_MANUAL, +2015-01-16,UNKNOWN,YES_MANUAL, 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 43dad6954..34bd58467 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 @@ -18,6 +18,7 @@ */ package org.isoron.uhabits.core.io +import com.opencsv.CSVWriter import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.EntryList import org.isoron.uhabits.core.models.Habit @@ -32,7 +33,6 @@ import java.io.FileOutputStream import java.io.FileWriter import java.io.IOException import java.io.Writer -import java.util.ArrayList import java.util.LinkedList import java.util.Locale import java.util.zip.ZipEntry @@ -109,11 +109,14 @@ class HabitsCSVExporter( var oldest = today val known = habit.computedEntries.getKnown() if (known.isNotEmpty()) oldest = known[known.size - 1].timestamp + val csv = CSVWriter(out) + csv.writeNext(arrayOf("Date", "Score"), false) for ((timestamp1, value) in habit.scores.getByInterval(oldest, today)) { val timestamp = dateFormat.format(timestamp1.unixTime) val score = String.format(Locale.US, "%.4f", value) - out.write(String.format("%s,%s\n", timestamp, score)) + csv.writeNext(arrayOf(timestamp, score), false) } + csv.close() out.close() } @@ -122,10 +125,20 @@ class HabitsCSVExporter( val out = FileWriter(exportDirName + filename) generatedFilenames.add(filename) val dateFormat = DateFormats.getCSVDateFormat() - for ((timestamp, value) in entries.getKnown()) { - val date = dateFormat.format(timestamp.toJavaDate()) - out.write(String.format(Locale.US, "%s,%d\n", date, value)) + val csv = CSVWriter(out) + csv.writeNext(arrayOf("Date", "Value", "Notes"), false) + for (entry in entries.getKnown()) { + val date = dateFormat.format(entry.timestamp.toJavaDate()) + csv.writeNext( + arrayOf( + date, + entry.formattedValue, + entry.notes + ), + false + ) } + csv.close() out.close() } @@ -167,7 +180,7 @@ class HabitsCSVExporter( checksWriter.write(sb.toString()) scoresWriter.write(sb.toString()) for (j in selectedHabits.indices) { - checksWriter.write(checkmarks[j][i].value.toString()) + checksWriter.write(checkmarks[j][i].formattedValue) checksWriter.write(delimiter) val score = String.format(Locale.US, "%.4f", scores[j][i].value) scoresWriter.write(score) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Entry.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Entry.kt index 99eb81950..078c11b9c 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Entry.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/Entry.kt @@ -23,6 +23,16 @@ data class Entry( val value: Int, val notes: String = "" ) { + val formattedValue: String + get() = when (value) { + YES_MANUAL -> "YES_MANUAL" + YES_AUTO -> "YES_AUTO" + NO -> "NO" + SKIP -> "SKIP" + UNKNOWN -> "UNKNOWN" + else -> value.toString() + } + companion object { /** * Value indicating that the habit is not applicable for this timestamp. 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 index fffe4ac85..9abe5546d 100644 --- 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 @@ -35,6 +35,10 @@ class HabitFixtures(private val modelFactory: ModelFactory, private val habitLis true, false, false, true, true, true, false, false, true, true ) + private var NON_DAILY_HABIT_NOTES = arrayOf( + "", "Sick", "Forgot to do it, really", "", "", "", "\"Vacation\"", "", "", "" + ) + fun createEmptyHabit( name: String = "Meditate", color: PaletteColor = PaletteColor(3), @@ -141,10 +145,10 @@ class HabitFixtures(private val modelFactory: ModelFactory, private val habitLis habit.frequency = Frequency(2, 3) saveIfSQLite(habit) var timestamp = getToday() - for (c in NON_DAILY_HABIT_CHECKS) { + for (i in NON_DAILY_HABIT_CHECKS.indices) { var value = Entry.NO - if (c) value = Entry.YES_MANUAL - habit.originalEntries.add(Entry(timestamp, value)) + if (NON_DAILY_HABIT_CHECKS[i]) value = Entry.YES_MANUAL + habit.originalEntries.add(Entry(timestamp, value, NON_DAILY_HABIT_NOTES[i])) timestamp = timestamp.minus(1) } habit.recompute() 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 index 5362e640e..55d16b2d9 100644 --- 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 @@ -108,12 +108,12 @@ class HabitsCSVExporterTest : BaseUnitTest() { private fun assertFileAndReferenceAreEqual(s: String) { val assetFilename = String.format("csv_export/%s", s) - val expectedFile = File(String.format("%s/%s", baseDir.absolutePath, s)) - val actualFile = File.createTempFile("asset", "") - actualFile.deleteOnExit() - copyAssetToFile(assetFilename, actualFile) - val expectedContents = expectedFile.readText() + val actualFile = File(String.format("%s/%s", baseDir.absolutePath, s)) + val expectedFile = File.createTempFile("asset", "") + expectedFile.deleteOnExit() + copyAssetToFile(assetFilename, expectedFile) val actualContents = actualFile.readText() + val expectedContents = expectedFile.readText() assertEquals(expectedContents, actualContents, "content mismatch for $s") } } From 0daa4f6a2facfec064fdba0989ea73b0c04e22d6 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 25 Jun 2025 22:20:58 -0500 Subject: [PATCH 07/17] Format source code --- .../jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt index 6227df991..227851803 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/HabitList.kt @@ -192,7 +192,7 @@ abstract class HabitList : Iterable { "Unit", "Target Type", "Target Value", - "Archived?", + "Archived?" ) val csv = CSVWriter(out) csv.writeNext(header, false) @@ -210,7 +210,7 @@ abstract class HabitList : Iterable { if (habit.isNumerical) habit.unit else "", if (habit.isNumerical) habit.targetType.name else "", if (habit.isNumerical) habit.targetValue.toString() else "", - habit.isArchived.toString(), + habit.isArchived.toString() ) csv.writeNext(cols, false) } From 96e20f751f945ea052d6ffc30cdfd510aa52ff2c Mon Sep 17 00:00:00 2001 From: powerjungle Date: Fri, 11 Apr 2025 17:18:26 +0200 Subject: [PATCH 08/17] Fix(UI): X and ? symbols easier to distinguish in "pure black" dark mode In addition to that, make "progress ring" a bit more visible. --- .../org/isoron/uhabits/activities/common/views/RingView.kt | 2 +- uhabits-android/src/main/res/values/styles.xml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/RingView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/RingView.kt index 59225dd2f..9a4b24bf9 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/RingView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/RingView.kt @@ -204,7 +204,7 @@ class RingView : View { val res = StyledResources(context) if (backgroundColor == null) backgroundColor = res.getColor(R.attr.cardBgColor) if (inactiveColor == null) inactiveColor = res.getColor(R.attr.contrast100) - inactiveColor = setAlpha(inactiveColor!!, 0.1f) + inactiveColor = setAlpha(inactiveColor!!, 0.15f) rect = RectF() } diff --git a/uhabits-android/src/main/res/values/styles.xml b/uhabits-android/src/main/res/values/styles.xml index 79e0b641c..f49ef9ad0 100644 --- a/uhabits-android/src/main/res/values/styles.xml +++ b/uhabits-android/src/main/res/values/styles.xml @@ -126,8 +126,9 @@ @color/black @color/black @color/grey_900 - @color/grey_700 - @color/grey_700 + @color/grey_800 + @color/grey_500 + @color/grey_400 @color/grey_200 @drawable/selected_box @color/grey_100 From 074627f6e14ba9c4795c77209bd4d052b4d62d2d Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 28 Jun 2025 16:03:22 -0500 Subject: [PATCH 09/17] Disable confetti if animations are disabled globally Fixes #2170 --- .../activities/habits/list/ListHabitsScreen.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt index 074f22f75..e54e06c86 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt @@ -23,6 +23,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle +import android.provider.Settings import androidx.appcompat.app.AppCompatActivity import dagger.Lazy import nl.dionsegijn.konfetti.core.Party @@ -226,6 +227,12 @@ class ListHabitsScreen override fun showConfetti(color: PaletteColor, x: Float, y: Float) { if (x == 0f && y == 0f) return if (preferences.isConfettiAnimationDisabled) return + if (Settings.Global.getFloat( + activity.contentResolver, + Settings.Global.ANIMATOR_DURATION_SCALE, + 1f + ) == 0f + ) return val baseColor = themeSwitcher.currentTheme!!.color(color).toInt() rootView.get().konfettiView.start( Party( @@ -299,30 +306,36 @@ class ListHabitsScreen command.selected.size ) } + is ChangeHabitColorCommand -> { return activity.resources.getQuantityString( R.plurals.toast_habits_changed, command.selected.size ) } + is CreateHabitCommand -> { return activity.resources.getString(R.string.toast_habit_created) } + is DeleteHabitsCommand -> { return activity.resources.getQuantityString( R.plurals.toast_habits_deleted, command.selected.size ) } + is EditHabitCommand -> { return activity.resources.getQuantityString(R.plurals.toast_habits_changed, 1) } + is UnarchiveHabitsCommand -> { return activity.resources.getQuantityString( R.plurals.toast_habits_unarchived, command.selected.size ) } + else -> return null } } @@ -335,9 +348,11 @@ class ListHabitsScreen adapter.refresh() activity.showMessage(activity.resources.getString(R.string.habits_imported)) } + ImportDataTask.NOT_RECOGNIZED -> { activity.showMessage(activity.resources.getString(R.string.file_not_recognized)) } + else -> { activity.showMessage(activity.resources.getString(R.string.could_not_import)) } From a6cf43dbca2738fcbe9a961a78f1bade437178c7 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 28 Jun 2025 16:05:43 -0500 Subject: [PATCH 10/17] Reformat source code --- .../isoron/uhabits/activities/habits/list/ListHabitsScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt index e54e06c86..14e53298b 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt @@ -232,7 +232,9 @@ class ListHabitsScreen Settings.Global.ANIMATOR_DURATION_SCALE, 1f ) == 0f - ) return + ) { + return + } val baseColor = themeSwitcher.currentTheme!!.color(color).toInt() rootView.get().konfettiView.start( Party( From 5403b6bd51fb5b6e4f229903d185c27287c084dc Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 28 Jun 2025 18:33:47 -0500 Subject: [PATCH 11/17] CheckmarkWidgetViewTest: Ignore non-deterministic test case --- .../org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.kt b/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.kt index 628a2ebbf..392058714 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.kt +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.kt @@ -25,6 +25,7 @@ import org.isoron.uhabits.R import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset import org.isoron.uhabits.utils.PaletteUtils.getAndroidTestColor import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import java.io.IOException @@ -57,6 +58,7 @@ class CheckmarkWidgetViewTest : BaseViewTest() { @Test @Throws(IOException::class) + @Ignore("non-deterministic") fun testRender_checked() { assertRenders(view, PATH + "checked.png") } From e608c6ea623a9bc4483fe502952d00d9faf92f26 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 28 Jun 2025 19:27:17 -0500 Subject: [PATCH 12/17] Trim unit labels when necessary --- .../habits/list/views/NumberButtonView.kt | 14 +++++++++++++- .../src/main/res/layout/show_habit_subtitle.xml | 14 +++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt index 03fe66e53..be2baa446 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.kt @@ -189,16 +189,19 @@ class NumberButtonView( textSize = dim(R.dimen.smallTextSize) typeface = getFontAwesome() } + value >= 0 -> { label = value.toShortString() typeface = BOLD_TYPEFACE textSize = dim(R.dimen.smallTextSize) } + preferences.areQuestionMarksEnabled -> { label = resources.getString(R.string.fa_question) typeface = getFontAwesome() textSize = dim(R.dimen.smallerTextSize) } + else -> { label = "0" typeface = BOLD_TYPEFACE @@ -212,14 +215,23 @@ class NumberButtonView( pUnit.color = activeColor if (units.isBlank()) { + // Draw number without units rect.set(0f, 0f, width.toFloat(), height.toFloat()) rect.offset(0f, 0.5f * em) canvas.drawText(label, rect.centerX(), rect.centerY(), pNumber) } else { + // Draw number rect.set(0f, 0f, width.toFloat(), height.toFloat()) canvas.drawText(label, rect.centerX(), rect.centerY(), pNumber) + + // Draw units + val maxUnitsWidth = width * 0.9f + var trimmedUnits = units + while (trimmedUnits.length > 2 && pUnit.measureText(trimmedUnits) > maxUnitsWidth) { + trimmedUnits = trimmedUnits.dropLast(2) + "…" + } rect.offset(0f, 1.3f * em) - canvas.drawText(units, rect.centerX(), rect.centerY(), pUnit) + canvas.drawText(trimmedUnits, rect.centerX(), rect.centerY(), pUnit) } drawNotesIndicator(canvas, color, em, notes) diff --git a/uhabits-android/src/main/res/layout/show_habit_subtitle.xml b/uhabits-android/src/main/res/layout/show_habit_subtitle.xml index 7361b4565..837b264d9 100644 --- a/uhabits-android/src/main/res/layout/show_habit_subtitle.xml +++ b/uhabits-android/src/main/res/layout/show_habit_subtitle.xml @@ -1,5 +1,4 @@ - - - + android:layout_alignParentTop="true" /> + style="@style/ShowHabit.Subtitle" /> + android:paddingTop="12dp" /> + android:paddingTop="12dp" /> + android:gravity="center" /> + android:gravity="center" /> + android:paddingBottom="0dp" /> + style="@style/Card" /> + style="@style/Card" /> From d57de9d10cc68357d53ae2d40e6b36af2924be70 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 8 Jul 2025 21:01:35 -0500 Subject: [PATCH 14/17] Apply bottom insets to about and settings screens --- .../uhabits/activities/about/AboutView.kt | 2 + .../activities/settings/SettingsActivity.kt | 2 + uhabits-android/src/main/res/layout/about.xml | 127 ++++++++++++------ 3 files changed, 90 insertions(+), 41 deletions(-) diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/AboutView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/AboutView.kt index 27a01225a..0df0a5524 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/AboutView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/about/AboutView.kt @@ -26,6 +26,7 @@ import org.isoron.uhabits.BuildConfig import org.isoron.uhabits.R import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.databinding.AboutBinding +import org.isoron.uhabits.utils.applyBottomInset import org.isoron.uhabits.utils.applyRootViewInsets import org.isoron.uhabits.utils.currentTheme import org.isoron.uhabits.utils.setupToolbar @@ -55,6 +56,7 @@ class AboutView( binding.tvTranslate.setOnClickListener { screen.showTranslationWebsite() } binding.tvVersion.setOnClickListener { screen.onPressDeveloperCountdown() } binding.tvVersion.text = String.format(version, BuildConfig.VERSION_NAME) + binding.outerLinearLayout.applyBottomInset() applyRootViewInsets() } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsActivity.kt index 684310cb6..8b9b77480 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsActivity.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/settings/SettingsActivity.kt @@ -26,6 +26,7 @@ import org.isoron.uhabits.R import org.isoron.uhabits.activities.AndroidThemeSwitcher import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.databinding.SettingsActivityBinding +import org.isoron.uhabits.utils.applyBottomInset import org.isoron.uhabits.utils.applyRootViewInsets import org.isoron.uhabits.utils.setupToolbar @@ -44,6 +45,7 @@ class SettingsActivity : AppCompatActivity() { theme = themeSwitcher.currentTheme ) binding.root.applyRootViewInsets() + binding.root.applyBottomInset() setContentView(binding.root) } } diff --git a/uhabits-android/src/main/res/layout/about.xml b/uhabits-android/src/main/res/layout/about.xml index 3eb152757..2dec3ae86 100644 --- a/uhabits-android/src/main/res/layout/about.xml +++ b/uhabits-android/src/main/res/layout/about.xml @@ -1,5 +1,4 @@ - - + android:layout_width="fill_parent" + android:layout_height="fill_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:background="?windowBackgroundColor" + android:fillViewport="true"> + style="@style/Toolbar" /> + android:src="@drawable/intro_icon_1" /> + android:text="@string/app_name" /> + android:text="" /> @@ -75,32 +75,32 @@ + android:textColor="?aboutScreenColor" /> + android:text="@string/pref_rate_this_app" /> + android:text="@string/pref_send_feedback" /> + android:text="@string/help_translate" /> + android:text="@string/pref_view_source_code" /> + android:id="@+id/tvPrivacy" + style="@style/About.Item.Clickable" + android:text="@string/pref_view_privacy" /> @@ -111,7 +111,7 @@ + android:textColor="?aboutScreenColor" /> - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + -> + > From e121f46b61830198d2a8c787cb6067549e2e472f Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 8 Jul 2025 21:17:23 -0500 Subject: [PATCH 15/17] Confetti: Fix position in freeform and landscape modes --- .../activities/habits/list/views/HabitCardView.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 eea1244eb..2edf17225 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 @@ -238,17 +238,18 @@ class HabitCardView( private fun getAbsoluteButtonLocation(timestamp: Timestamp): PointF { val containerLocation = IntArray(2) - this.getLocationOnScreen(containerLocation) + this.getLocationInWindow(containerLocation) val relButtonLocation = getRelativeButtonLocation(timestamp) val windowInsets = rootWindowInsets - val statusBarHeight = if (SDK_INT <= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + val xInset = windowInsets?.displayCutout?.safeInsetLeft ?: 0 + val yInset = if (SDK_INT <= Build.VERSION_CODES.VANILLA_ICE_CREAM) { windowInsets?.systemWindowInsetTop ?: 0 } else { 0 } return PointF( - containerLocation[0].toFloat() + relButtonLocation.x, - containerLocation[1].toFloat() + relButtonLocation.y - statusBarHeight + containerLocation[0].toFloat() + relButtonLocation.x - xInset, + containerLocation[1].toFloat() + relButtonLocation.y - yInset ) } From a9acbd6cabd6da33d2f0b8363f9651d7e75c2d37 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 8 Jul 2025 21:28:54 -0500 Subject: [PATCH 16/17] HabitCardListView: Prevent duplicate inset decorations --- .../uhabits/activities/habits/list/views/HabitCardListView.kt | 3 +++ 1 file changed, 3 insertions(+) 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 b2ef1a92f..9b5c7ca43 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 @@ -60,6 +60,7 @@ class HabitCardListView( ) : RecyclerView(context, null, R.attr.scrollableRecyclerViewStyle) { var checkmarkCount: Int = 0 + private var insetDecorationsAdded: Boolean = false var dataOffset: Int = 0 set(value) { @@ -84,6 +85,8 @@ class HabitCardListView( private fun applyBottomInset() { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> + if (insetDecorationsAdded) return@setOnApplyWindowInsetsListener insets + insetDecorationsAdded = true val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) addItemDecoration(object : ItemDecoration() { override fun getItemOffsets( From 2816b7c3d04b61277dab8765d17420f878241f09 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Wed, 13 Aug 2025 21:08:29 -0500 Subject: [PATCH 17/17] Bump version to 2.3.1 and update changelog --- CHANGELOG.md | 56 +++++++++++++++++++++++++++++--- uhabits-android/build.gradle.kts | 4 +-- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 775548b99..c37626019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,32 +1,54 @@ # Changelog +## [2.3.1] -- 2025-08-13 + +### Changed + +- Add notes to exported CSV files (@iSoron) + +### Fixed + +- Prevent some views from being obscured by system UI (@iSoron, #2171) +- Disable confetti if animations are disabled globally (@iSoron, #2170) +- Make symbols easier to distinguish in "pure black" dark mode (powerjungle, #2136) +- Trim unit labels when necessary (@hiqua, @iSoron, #2158) + ## [2.3.0] -- 2025-06-23 + ### Added + - Add support for Android 15 and 16 (@iSoron) - Show confetti animation (@gokulk16, @iSoron, #1743) - Show streaks for measurable habits (@teckwarz, #2059) - Allow user to unset measurable habits (@leontodd, @kalina559, #1899, #2109) ### Changed + - Change background widget color for habits with implicit checks (@wobbba, #1915) ### Fixed + - Fix notification when goal type is set to maximum (@manish99verma, #1931) - Never mark "at most" habits as completed for the day (@kalina559, #2077) - Increase minimum widget size (@iSoron, #2118) - Improve Gradle configuration (@jimlyas, #2108) ## [2.2.0] -- 2024-01-30 + ### Added + - Add support for Android 14 (@iSoron, @hiqua) - Allow user to change app language (@leondzn) ### Fixed + - Implement workaround to make notifications non-dismissible in Android 14 (@iSoron, #1872) - Fix splash screen background color in dark mode (@SIKV, #1888) ## [2.1.3] -- 2023-08-28 + ### Fixed + - Use text input on Samsung devices (@iSoron, #1719) - Prevent crash if alarm permission is revoked (@iSoron) - Adjust widget colors (@iSoron) @@ -34,7 +56,9 @@ - Fix skip button in locales that use comma instead of dot (@iSoron, #1721) ## [2.1.2] -- 2023-05-26 + ### Fixed + - Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541) - Fix widget corners on Android 12 (@iSoron) - Fix bug that caused notes to be lost when editing a checkmark (@iSoron, #1566) @@ -42,18 +66,23 @@ - Accept comma (instead of dot) in certain locales (@iSoron) ### Changed + - Remove update delay after entering a checkmark (@iSoron) ### Removed -- Remove stack widgets (@iSoron) +- Remove stack widgets (@iSoron) ## [2.1.1] -- 2022-09-24 + ### Fixed + - Fix Tasker plugin (@iSoron, #1503) ## [2.1.0] -- 2022-09-10 + ### Added + - Allow user to add notes to specific dates (@vbh, #1103) - Allow user to track "at most" numerical habits (@KristianTashkov, #1101) - Allow user to add skips to measurable habits (@kalina559, #1319) @@ -64,10 +93,12 @@ - Add support for Android 13 themed icons (@cheeeeer, #1497) ### Removed + - Hide snooze button Android 12 notifications (@hiqua, #1226) - Remove preference to set LED lights (@iSoron) ### Changed + - Hide failed habits along with completed ones (@hiqua, #1052) - Cycle through all checkmark states when toggling (@iSoron) - Add delay after toggling a habit (@hiqua, @kalina559, #1147) @@ -76,6 +107,7 @@ - Increase target SDK to 31 (@hiqua) ### Fixed + - Fix small dialog buttons (@kalina559, #1096) - Fix invalid CSV files (@hiqua, #1177) - Fix small issues in calendar chart (@kalina559, #1314) @@ -84,13 +116,16 @@ - Fix widgets not working correctly on API 33 (@iSoron, #1488) ### Refactoring & Testing + - Replace raster icons by vector assets (@kalina559) - Remove JVM dependencies from uhabits-core module (@sgallese) - Add various missing tests (@sgallese) - Upgrade project dependencies (@hiqua, @sgallese) ## [2.0.3] - 2021-08-21 + ### Fixed + - Improve automatic checkmarks for monthly habits (@iSoron, #947) - Fix small theme issues (@iSoron) - Fix ANR on some Samsung phones (@iSoron, #962) @@ -102,9 +137,11 @@ ## [2.0.2] - 2021-05-23 ### Changed + - Make checkmark widget resizable ### Fixed + - Fix crash caused by numerical habits with zero target (@iSoron, #903) - Fix small issues with font size (@iSoron) - Allow fractional target values (@sumanabhi, #911) @@ -115,18 +152,22 @@ ## [2.0.1] - 2021-05-09 ### Added + - Make midnight delay optional and disabled by default (@hiqua) -- Add arrows to sort menu (@iSoron) +- Add arrows to sort menu (@iSoron) ### Removed + - Temporarily remove experimental device sync functionality. This feature will be re-added in Loop 2.1. ### Changed + - Make implicit checkmarks easier to read (@iSoron) - Update and improve list of translators (@hiqua, @iSoron) ### Fixed + - Disable transparency for stacked widgets (@hiqua) - Fix various color issues on the dark theme (@hiqua, @iSoron) - Fix "customize notifications" on older devices (@hiqua) @@ -135,6 +176,7 @@ - Fix checkmark widget not rendering properly on some Samsung phones (@iSoron) ### Refactoring & Testing + - Finish conversion of the entire project to Kotlin (@hiqua, @iSoron, @MarKco) - Automatically run large tests on GitHub Actions (@iSoron) - Remove unused v21 resources (@hiqua) @@ -142,6 +184,7 @@ ## [2.0.0-alpha] - 2020-11-29 ### Added + - Track numeric habits (@iSoron, @namnl) - Skip days without breaking streak (@KristianTashkov) - Sort habits by status (@hiqua) @@ -152,15 +195,18 @@ - Export backups daily (@iSoron) ### Removed + - Drop support to devices older than Android 6.0 (API 23) ### Fixed + - Reset chart offset when switching scale (@alxmjo) - Don't show reminders from archived habits (@KristianTashkov) - Lapses on non-daily habits decrease the score too much (@iSoron) - Update widgets at midnight (@KristianTashkov) ### Refactoring + - Convert files to Kotlin (@olegivo) ## [1.8.12] - 2021-01-30 @@ -185,13 +231,15 @@ ## [1.8.8] - 2020-06-21 -- Make small changes to the habit scheduling algorithm, so that "1 time every x days" habits work more predictably. +- Make small changes to the habit scheduling algorithm, so that "1 time every x days" habits work + more predictably. - Fix crash when saving habit ## [1.8.0] - 2020-01-01 - New bar chart showing number of repetitions performed in each week, month, quarter or year. -- Improved calculation of streaks for non-daily habits: performing habits on irregular weekdays will no longer break your streak. +- Improved calculation of streaks for non-daily habits: performing habits on irregular weekdays will + no longer break your streak. - Many more colors to choose from (now 20 in total). - Ability to customize how transparent the widgets are on your home screen. - Ability to customize the first day of the week. diff --git a/uhabits-android/build.gradle.kts b/uhabits-android/build.gradle.kts index bffb150bb..498c0881b 100644 --- a/uhabits-android/build.gradle.kts +++ b/uhabits-android/build.gradle.kts @@ -44,8 +44,8 @@ android { compileSdk = 36 defaultConfig { - versionCode = 20300 - versionName = "2.3.0" + versionCode = 20301 + versionName = "2.3.1" minSdk = 28 targetSdk = 36 applicationId = "org.isoron.uhabits"