Compare commits

..

22 Commits

Author SHA1 Message Date
70dab74528 Bump version to 2.3.0 and update changelog 2025-06-23 22:23:55 -05:00
7e5d2fa207 Update Gradle and AGP versions to 8.11.1 and 8.9.2 2025-06-23 21:48:00 -05:00
0e432fb332 HistoryWidget: Increase padding; update widget images 2025-06-23 21:47:47 -05:00
897a236501 Widgets: Update test images 2025-03-23 19:41:31 -05:00
0cccecec77 Widgets: Update test images 2025-03-23 19:27:12 -05:00
f1ed875256 Further increase widget corner radius to match current Android style 2025-03-23 17:10:39 -05:00
e82bd47aab Increase minimum widget size to 50x50 and 100x100
Some Samsung phones were allowing graph widgets to occupy 1x2 or
2x1 grid cells, leading to very small text. This commit bumps up
the minimum widget size to 100x100 to ensure they always occupy at
least 2x2 cells. Tested on Pixel 4, Pixel 7 and Samsung Galaxy S24.

Closes #2118
2025-03-23 16:52:23 -05:00
e9517f7378 Bump targetSdk to 36 2025-03-23 07:22:36 -05:00
12cc70a51a Confetti: Always emit from checkmark, not popup button 2025-03-23 07:06:45 -05:00
fa670b19b7 HabitCardView: Fix confetti position in API 36+ 2025-03-22 23:03:59 -05:00
45b100aad9 build.sh: Make output dir 2025-03-22 15:26:28 -05:00
3c0c0b77ff build.sh: Update emulator path 2025-03-22 15:19:37 -05:00
66fa56ea62 Merge branch 'improve-gradle' into dev 2025-03-21 22:16:29 -05:00
951dabea8b Merge branch 'isse_1857_reset_measurable_entry' into dev 2025-03-21 21:41:55 -05:00
76b9dd8bd9 Checkmark popup: Minor changes to layout 2025-03-21 21:41:23 -05:00
f68510f860 Allow user to disable confetti animation 2025-03-21 21:29:49 -05:00
kalina559
cc720e3dcb Removed an unnecessary change 2025-02-18 23:19:20 +01:00
kalina559
6e3d06cff9 Corrected formatting in the layout file 2025-02-18 22:32:48 +01:00
kalina559
d458cbd47a COrrected formatting 2025-02-18 20:51:16 +01:00
kalina559
74ce269446 Added 'UNKNOWN' button for measurable habits 2025-02-18 20:47:12 +01:00
Jimly Asshiddiqy
9eb8624863 Migrate to KSP 2025-02-17 09:56:21 +07:00
Jimly Asshiddiqy
c4bc301fb2 Implement version catalog 2025-02-14 16:56:04 +07:00
49 changed files with 320 additions and 184 deletions

View File

@@ -1,5 +1,21 @@
# Changelog
## [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)

View File

@@ -1,23 +1,11 @@
plugins {
val kotlinVersion = "2.1.10"
id("com.android.application") version "8.8.0" apply (false)
id("org.jetbrains.kotlin.android") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.kapt") version kotlinVersion apply (false)
id("org.jetbrains.kotlin.multiplatform") version kotlinVersion apply (false)
id("org.jlleitschuh.gradle.ktlint") version "11.6.1"
alias(libs.plugins.agp) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.ktlint.plugin) apply false
alias(libs.plugins.shadow) apply false
}
apply {
from("translators.gradle.kts")
}
allprojects {
repositories {
google()
mavenCentral()
maven(url = "https://plugins.gradle.org/m2/")
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
maven(url = "https://jitpack.io")
maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-js-wrappers/") // Repository for kotlin-css-jvm old versions, now that the Gradle Plugin Portal no longer brings these in by mirroring JCenter
}
from("gradle/translators.gradle.kts")
}

View File

@@ -21,7 +21,7 @@ ADB="${ANDROID_HOME}/platform-tools/adb"
ANDROID_OUTPUTS_DIR="uhabits-android/build/outputs"
AVDMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/avdmanager"
AVD_PREFIX="uhabitsTest"
EMULATOR="${ANDROID_HOME}/tools/emulator"
EMULATOR="${ANDROID_HOME}/emulator/emulator"
GRADLE="./gradlew --stacktrace --quiet"
PACKAGE_NAME=org.isoron.uhabits
SDKMANAGER="${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager"
@@ -38,6 +38,11 @@ if [ ! -f "${ANDROID_HOME}/platform-tools/adb" ]; then
exit 1
fi
if [ ! -f "$EMULATOR" ]; then
echo "Error: Not found: $EMULATOR"
exit 1
fi
# Logging
# -----------------------------------------------------------------------------
@@ -217,6 +222,7 @@ android_test_parallel() {
for API in $*; do
(
LOG=build/android-test-$API.log
mkdir -p build
log_info "API $API: Running tests..."
android_test $API 1>$LOG 2>&1
ret_code=$?

View File

@@ -6,3 +6,6 @@ android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
org.gradle.configureondemand=true
org.gradle.warning.mode=all
org.gradle.caching=true

102
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,102 @@
[versions]
agp = "8.9.2"
annotation = "1.9.1"
appcompat = "1.7.0"
appintro = "6.3.1"
commonsCodec = "1.16.0"
commonsIo = "1.3.2"
commonsLang3 = "3.14.0"
dagger = "2.55"
desugar = "2.1.4"
dexmaker = "2.28.3"
espresso = "3.6.1"
guava = "33.2.1-android"
hamcrest = "2.2"
jsr250 = "1.0"
jsr305 = "3.0.2"
junit = "1.2.1"
junitJupiter = "5.10.1"
junitVersion = "4.13.2"
konfetti-xml = "2.0.2"
kotlin = "2.1.10"
kotlinxCoroutinesCoreCommon = "1.3.8"
ksp = "2.1.10-1.0.30"
ktlint-plugin = "11.6.1"
ktor = "1.6.8"
ktxCoroutine = "1.10.1"
legacy-support = "1.0.0"
material = "1.12.0"
mockito-kotlin = "5.4.0"
opencsv = "5.9"
rules = "1.6.1"
shadow = "8.1.1"
sqliteJdbc = "3.45.1.0"
uiautomator = "2.3.0"
[libraries]
annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
appIntro = { group = "com.github.AppIntro", name = "AppIntro", version.ref = "appintro" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
commons-codec = { module = "commons-codec:commons-codec", version.ref = "commonsCodec" }
commons-io = { module = "org.apache.commons:commons-io", version.ref = "commonsIo" }
commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commonsLang3" }
dagger = { group = "com.google.dagger", name = "dagger", version.ref = "dagger" }
dagger-compiler = { group = "com.google.dagger", name = "dagger-compiler", version.ref = "dagger" }
desugar_jdk_libs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar" }
dexmaker-mockito = { group = "com.linkedin.dexmaker", name = "dexmaker-mockito", version.ref = "dexmaker" }
espresso-contrib = { group = "androidx.test.espresso", name = "espresso-contrib", version.ref = "espresso" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
guava = { group = "com.google.guava", name = "guava", version.ref = "guava" }
hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" }
jsr250-api = { group = "javax.annotation", name = "jsr250-api", version.ref = "jsr250" }
jsr305 = { group = "com.google.code.findbugs", name = "jsr305", version.ref = "jsr305" }
junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit" }
junit-junit = { module = "junit:junit", version.ref = "junitVersion" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junitJupiter" }
konfetti-xml = { group = "nl.dionsegijn", name = "konfetti-xml", version.ref = "konfetti-xml" }
kotlin-stdlib-jdk8 = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "ktxCoroutine" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "ktxCoroutine" }
kotlinx-coroutines-core-common = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-common", version.ref = "kotlinxCoroutinesCoreCommon" }
kotlinx-coroutines-core-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "ktxCoroutine" }
ktor-client-android = { group = "io.ktor", name = "ktor-client-android", version.ref = "ktor" }
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
ktor-client-jackson = { group = "io.ktor", name = "ktor-client-jackson", version.ref = "ktor" }
ktor-client-json = { group = "io.ktor", name = "ktor-client-json", version.ref = "ktor" }
ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" }
ktor-jackson = { group = "io.ktor", name = "ktor-jackson", version.ref = "ktor" }
legacy-preference-v14 = { group = "androidx.legacy", name = "legacy-preference-v14", version.ref = "legacy-support" }
legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacy-support" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockito-kotlin" }
opencsv = { group = "com.opencsv", name = "opencsv", version.ref = "opencsv" }
rules = { group = "androidx.test", name = "rules", version.ref = "rules" }
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqliteJdbc" }
uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
[bundles]
androidTest = [
"annotation",
"dagger",
"dexmaker-mockito",
"espresso-contrib",
"espresso-core",
"junit",
"ktor-client-mock",
"ktor-jackson",
"mockito-kotlin",
"rules",
"uiautomator"
]
test = [
"dagger",
"junit-junit",
"mockito-kotlin",
]
[plugins]
agp = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
ktlint-plugin = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint-plugin" }
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,13 +1,32 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
}
resolutionStrategy.eachPlugin {
if (requested.id.id == "com.android.application") {
useModule("com.android.tools.build:gradle:${requested.version}")
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
}
}
include(":uhabits-android", ":uhabits-core")
dependencyResolutionManagement {
@Suppress("UnstableApiUsage")
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
maven(url = "https://plugins.gradle.org/m2/")
maven(url = "https://oss.sonatype.org/content/repositories/snapshots/")
maven(url = "https://jitpack.io")
}
}
include(":uhabits-android", ":uhabits-core")

View File

@@ -18,10 +18,10 @@
*/
plugins {
id("com.android.application") version "8.8.0"
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.kapt")
id("org.jlleitschuh.gradle.ktlint")
alias(libs.plugins.agp)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.ksp)
alias(libs.plugins.ktlint.plugin)
}
tasks.compileLint {
@@ -40,16 +40,14 @@ kotlin {
}
android {
namespace = "org.isoron.uhabits"
compileSdk = 35
// compileSdkPreview = "VanillaIceCream"
compileSdk = 36
defaultConfig {
versionCode = 20200
versionName = "2.2.0"
versionCode = 20300
versionName = "2.3.0"
minSdk = 28
targetSdk = 35
targetSdk = 36
applicationId = "org.isoron.uhabits"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -66,7 +64,7 @@ android {
}
buildTypes {
getByName("release") {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.txt")
if (signingConfigs.findByName("release") != null) {
@@ -74,8 +72,8 @@ android {
}
}
getByName("debug") {
isTestCoverageEnabled = true
debug {
enableUnitTestCoverage = true
}
}
@@ -84,64 +82,35 @@ android {
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_11)
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
buildFeatures {
viewBinding = true
}
lint {
abortOnError = false
}
kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString()
buildFeatures.viewBinding = true
lint.abortOnError = false
}
dependencies {
val daggerVersion = "2.51.1"
val kotlinVersion = "2.1.10"
val kxCoroutinesVersion = "1.10.1"
val ktorVersion = "1.6.8"
val espressoVersion = "3.6.1"
androidTestImplementation("androidx.test.espresso:espresso-contrib:$espressoVersion")
androidTestImplementation("androidx.test.espresso:espresso-core:$espressoVersion")
androidTestImplementation("com.google.dagger:dagger:$daggerVersion")
androidTestImplementation("com.linkedin.dexmaker:dexmaker-mockito:2.28.3")
androidTestImplementation("io.ktor:ktor-client-mock:$ktorVersion")
androidTestImplementation("io.ktor:ktor-jackson:$ktorVersion")
androidTestImplementation("androidx.annotation:annotation:1.7.1")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.3.0")
androidTestImplementation("androidx.test:rules:1.6.1")
androidTestImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
compileOnly("javax.annotation:jsr250-api:1.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
implementation("com.github.AppIntro:AppIntro:6.3.1")
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.google.dagger:dagger:$daggerVersion")
implementation("com.google.guava:guava:33.1.0-android")
implementation("io.ktor:ktor-client-android:$ktorVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-jackson:$ktorVersion")
implementation("io.ktor:ktor-client-json:$ktorVersion")
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.7.0")
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.12.0")
implementation("com.opencsv:opencsv:5.9")
implementation("nl.dionsegijn:konfetti-xml:2.0.2")
compileOnly(libs.jsr250.api)
coreLibraryDesugaring(libs.desugar.jdk.libs)
implementation(libs.appIntro)
implementation(libs.jsr305)
implementation(libs.dagger)
implementation(libs.guava)
implementation(libs.ktor.client.android)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.jackson)
implementation(libs.ktor.client.json)
implementation(libs.kotlin.stdlib.jdk8)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.appcompat)
implementation(libs.legacy.preference.v14)
implementation(libs.legacy.support.v4)
implementation(libs.material)
implementation(libs.opencsv)
implementation(libs.konfetti.xml)
implementation(project(":uhabits-core"))
kapt("com.google.dagger:dagger-compiler:$daggerVersion")
kaptAndroidTest("com.google.dagger:dagger-compiler:$daggerVersion")
testImplementation("com.google.dagger:dagger:$daggerVersion")
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
}
ksp(libs.dagger.compiler)
kapt {
correctErrorTypes = true
androidTestImplementation(libs.bundles.androidTest)
testImplementation(libs.bundles.test)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -33,11 +33,10 @@ import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
import org.isoron.uhabits.utils.getCenter
import org.isoron.uhabits.utils.sres
class CheckmarkDialog : AppCompatDialogFragment() {
var onToggle: (Int, String, Float, Float) -> Unit = { _, _, _, _ -> }
var onToggle: (Int, String) -> Unit = { _, _ -> }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val appComponent = (requireActivity().application as HabitsApplication).component
@@ -64,8 +63,7 @@ class CheckmarkDialog : AppCompatDialogFragment() {
}
fun onClick(v: Int) {
val notes = view.notes.text.toString().trim()
val location = view.yesBtn.getCenter()
onToggle(v, notes, location.x, location.y)
onToggle(v, notes)
requireDialog().dismiss()
}
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }

View File

@@ -25,7 +25,7 @@ import java.text.ParseException
class NumberDialog : AppCompatDialogFragment() {
var onToggle: (Double, String, Float, Float) -> Unit = { _, _, _, _ -> }
var onToggle: (Double, String) -> Unit = { _, _ -> }
var onDismiss: () -> Unit = {}
private var originalNotes: String = ""
@@ -36,16 +36,17 @@ class NumberDialog : AppCompatDialogFragment() {
val appComponent = (requireActivity().application as HabitsApplication).component
val prefs = appComponent.preferences
view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
arrayOf(view.yesBtn, view.skipBtn).forEach {
arrayOf(view.yesBtn).forEach {
it.setTextColor(requireArguments().getInt("color"))
}
arrayOf(view.noBtn, view.unknownBtn).forEach {
arrayOf(view.noBtn, view.unknownBtnNumber).forEach {
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
}
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
arrayOf(view.yesBtn, view.noBtn, view.unknownBtnNumber).forEach {
it.typeface = InterfaceUtils.getFontAwesome(requireContext())
}
if (!prefs.isSkipEnabled) view.skipBtnNumber.visibility = View.GONE
if (!prefs.areQuestionMarksEnabled) view.unknownBtnNumber.visibility = View.GONE
view.numberButtons.visibility = View.VISIBLE
fixDecimalSeparator(view)
originalNotes = requireArguments().getString("notes")!!
@@ -71,6 +72,12 @@ class NumberDialog : AppCompatDialogFragment() {
view.value.setText(DecimalFormat("#.###").format((Entry.SKIP.toDouble() / 1000)))
save()
}
view.unknownBtnNumber.setOnClickListener {
view.value.setText(DecimalFormat("#.###").format((Entry.UNKNOWN.toDouble() / 1000)))
save()
}
view.notes.setOnEditorActionListener { v, actionId, event ->
save()
true
@@ -115,7 +122,7 @@ class NumberDialog : AppCompatDialogFragment() {
}
val notes = view.notes.text.toString()
val location = view.saveBtn.getCenter()
onToggle(value, notes, location.x, location.y)
onToggle(value, notes)
requireDialog().dismiss()
}
}

View File

@@ -180,7 +180,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
val timestamp = intent.extras?.getLong("timestamp")
if (habitId != null && timestamp != null) {
val habit = appComponent.habitList.getById(habitId)!!
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp))
component.listHabitsBehavior.onEdit(habit, Timestamp(timestamp), 0f, 0f)
}
}
intent = null

View File

@@ -224,6 +224,8 @@ class ListHabitsScreen
}
override fun showConfetti(color: PaletteColor, x: Float, y: Float) {
if (x == 0f && y == 0f) return
if (preferences.isConfettiAnimationDisabled) return
val baseColor = themeSwitcher.currentTheme!!.color(color).toInt()
rootView.get().konfettiView.start(
Party(
@@ -267,7 +269,7 @@ class ListHabitsScreen
putDouble("value", value)
putString("notes", notes)
}
dialog.onToggle = { v, n, x, y -> callback.onNumberPicked(v, n, x, y) }
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
dialog.dismissCurrentAndShow(fm, "numberDialog")
}
@@ -285,7 +287,7 @@ class ListHabitsScreen
putInt("value", selectedValue)
putString("notes", notes)
}
dialog.onToggle = { v, n, x, y -> callback.onNotesSaved(v, n, x, y) }
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
dialog.dismissCurrentAndShow(fm, "checkmarkDialog")
}

View File

@@ -169,7 +169,8 @@ class HabitCardView(
}
onEdit = { timestamp ->
triggerRipple(timestamp)
habit?.let { behavior.onEdit(it, timestamp) }
val location = getAbsoluteButtonLocation(timestamp)
habit?.let { behavior.onEdit(it, timestamp, location.x, location.y) }
}
}
@@ -177,7 +178,8 @@ class HabitCardView(
visibility = GONE
onEdit = { timestamp ->
triggerRipple(timestamp)
habit?.let { behavior.onEdit(it, timestamp) }
val location = getAbsoluteButtonLocation(timestamp)
habit?.let { behavior.onEdit(it, timestamp, location.x, location.y) }
}
}
@@ -224,9 +226,13 @@ class HabitCardView(
private fun getRelativeButtonLocation(timestamp: Timestamp): PointF {
val today = DateUtils.getTodayWithOffset()
val offset = timestamp.daysUntil(today) - dataOffset
val button = checkmarkPanel.buttons[offset]
val panel = when (habit!!.isNumerical) {
true -> numberPanel
false -> checkmarkPanel
}
val button = panel.buttons[offset]
val y = button.height / 2.0f
val x = checkmarkPanel.x + button.x + (button.width / 2).toFloat()
val x = panel.x + button.x + (button.width / 2).toFloat()
return PointF(x, y)
}
@@ -234,9 +240,15 @@ class HabitCardView(
val containerLocation = IntArray(2)
this.getLocationOnScreen(containerLocation)
val relButtonLocation = getRelativeButtonLocation(timestamp)
val windowInsets = rootWindowInsets
val statusBarHeight = 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
containerLocation[1].toFloat() + relButtonLocation.y - statusBarHeight
)
}

View File

@@ -181,7 +181,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
putDouble("value", value)
putString("notes", notes)
}
dialog.onToggle = { v, n, x, y -> callback.onNumberPicked(v, n, x, y) }
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
dialog.dismissCurrentAndShow(supportFragmentManager, "numberDialog")
}
@@ -198,7 +198,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
putInt("value", selectedValue)
putString("notes", notes)
}
dialog.onToggle = { v, n, x, y -> callback.onNotesSaved(v, n, x, y) }
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
dialog.dismissCurrentAndShow(supportFragmentManager, "checkmarkDialog")
}

View File

@@ -75,7 +75,8 @@ class HistoryWidget(
firstWeekday = prefs.firstWeekday,
series = listOf(),
defaultSquare = HistoryChart.Square.OFF,
notesIndicators = listOf()
notesIndicators = listOf(),
padding = 2.5
)
}
).apply {

View File

@@ -69,7 +69,7 @@ abstract class HabitWidgetView : FrameLayout {
val shadowRadius = dpToPixels(context, 2f).toInt()
val shadowOffset = dpToPixels(context, 1f).toInt()
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
val cornerRadius = dpToPixels(context, 12f)
val cornerRadius = dpToPixels(context, 18f)
val radii = FloatArray(8)
Arrays.fill(radii, cornerRadius)
val shape = RoundRectShape(radii, null, null)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -28,7 +28,7 @@
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<corners android:radius="12dp"/>
<corners android:radius="18dp"/>
<solid android:color="?android:colorPrimary"/>
</shape>
<color android:color="@color/white"/>

View File

@@ -23,32 +23,32 @@
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="128dp"
android:background="@drawable/checkmark_dialog_bg"
android:minWidth="208dp"
app:divider="@drawable/checkmark_dialog_divider"
app:showDividers="middle"
android:minHeight="128dp"
android:orientation="vertical"
android:background="@drawable/checkmark_dialog_bg">
app:divider="@drawable/checkmark_dialog_divider"
app:showDividers="middle">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/notes"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:inputType="textCapSentences|textMultiLine"
android:textSize="@dimen/smallTextSize"
android:padding="4dp"
android:background="@color/transparent"
android:gravity="center"
android:hint="@string/notes"
android:text="" />
android:inputType="textCapSentences|textMultiLine"
android:padding="4dp"
android:text=""
android:textSize="@dimen/smallTextSize" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/booleanButtons"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:visibility="gone"
app:divider="@drawable/checkmark_dialog_divider"
app:showDividers="middle">
@@ -75,10 +75,10 @@
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/numberButtons"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
android:visibility="gone"
app:divider="@drawable/checkmark_dialog_divider"
app:showDividers="middle">
@@ -86,21 +86,26 @@
android:id="@+id/value"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_weight="2"
android:background="@color/transparent"
android:textAlignment="center"
android:inputType="numberDecimal"
android:selectAllOnFocus="true"
android:textAlignment="center"
android:textSize="@dimen/smallTextSize" />
<TextView
android:id="@+id/saveBtn"
style="@style/NumericalPopupBtn"
android:text="@string/save" />
<TextView
android:id="@+id/skipBtnNumber"
style="@style/NumericalPopupBtn"
android:text="@string/skip_day" />
<TextView
android:id="@+id/saveBtn"
style="@style/NumericalPopupBtn"
android:text="@string/save" />
android:id="@+id/unknownBtnNumber"
style="@style/CheckmarkPopupBtn"
android:text="@string/fa_question" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>

View File

@@ -233,4 +233,6 @@
<string name="activity_not_found">No app was found to support this action</string>
<string name="pref_midnight_delay_title">Extend day a few hours past midnight</string>
<string name="pref_midnight_delay_description">Wait until 3:00 AM to show a new day. Useful if you typically go to sleep after midnight. Requires app restart.</string>
<string name="pref_animations_title">Disable animations</string>
<string name="pref_animations_description">Disable confetti animation after adding a checkmark.</string>
</resources>

View File

@@ -67,6 +67,13 @@
android:title="@string/use_pure_black"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_disable_animation"
android:summary="@string/pref_animations_description"
android:title="@string/pref_animations_title"
app:iconSpaceReserved="false" />
<ListPreference
android:defaultValue="255"
android:entries="@array/widget_opacity_entries"

View File

@@ -19,8 +19,8 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="40dp"
android:minWidth="40dp"
android:minHeight="50dp"
android:minWidth="50dp"
android:initialLayout="@layout/widget_wrapper"
android:previewImage="@drawable/widget_preview_checkmark"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_frequency"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_history"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_score"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_streaks"
android:resizeMode="vertical|horizontal"

View File

@@ -19,10 +19,10 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="80dp"
android:minWidth="80dp"
android:minResizeWidth="80dp"
android:minResizeHeight="80dp"
android:minHeight="100dp"
android:minWidth="100dp"
android:minResizeWidth="100dp"
android:minResizeHeight="100dp"
android:initialLayout="@layout/widget_graph"
android:previewImage="@drawable/widget_preview_target"
android:resizeMode="vertical|horizontal"

View File

@@ -19,7 +19,7 @@
plugins {
kotlin("multiplatform")
id("org.jlleitschuh.gradle.ktlint")
alias(libs.plugins.ktlint.plugin)
}
kotlin {
@@ -30,7 +30,7 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.8")
implementation(libs.kotlinx.coroutines.core.common)
}
}
@@ -44,14 +44,14 @@ kotlin {
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
compileOnly("com.google.dagger:dagger:2.51.1")
implementation("com.google.guava:guava:33.1.0-android")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1")
implementation("androidx.annotation:annotation:1.7.1")
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.opencsv:opencsv:5.9")
implementation("commons-codec:commons-codec:1.16.0")
implementation("org.apache.commons:commons-lang3:3.14.0")
compileOnly(libs.dagger)
implementation(libs.guava)
implementation(libs.kotlinx.coroutines.core.jvm)
implementation(libs.annotation)
implementation(libs.jsr305)
implementation(libs.opencsv)
implementation(libs.commons.codec)
implementation(libs.commons.lang3)
}
}
@@ -59,19 +59,16 @@ kotlin {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
implementation("org.xerial:sqlite-jdbc:3.45.1.0")
implementation("org.hamcrest:hamcrest:2.2")
implementation("org.apache.commons:commons-io:1.3.2")
implementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
implementation("org.junit.jupiter:junit-jupiter:5.10.1")
implementation(libs.sqlite.jdbc)
implementation(libs.hamcrest)
implementation(libs.commons.io)
implementation(libs.mockito.kotlin)
implementation(libs.junit.jupiter)
}
}
}
}
tasks.named<org.gradle.language.jvm.tasks.ProcessResources>("jvmProcessResources") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
tasks.named<org.gradle.language.jvm.tasks.ProcessResources>("jvmTestProcessResources") {
tasks.withType<ProcessResources> {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

View File

@@ -135,6 +135,12 @@ open class Preferences(private val storage: Storage) {
storage.putBoolean("pref_short_toggle", enabled)
}
var isConfettiAnimationDisabled: Boolean
get() = storage.getBoolean("pref_disable_animation", false)
set(enabled) {
storage.putBoolean("pref_disable_animation", enabled)
}
fun removeListener(listener: Listener) {
listeners.remove(listener)
}

View File

@@ -51,11 +51,11 @@ open class ListHabitsBehavior @Inject constructor(
screen.showHabitScreen(h)
}
fun onEdit(habit: Habit, timestamp: Timestamp?) {
fun onEdit(habit: Habit, timestamp: Timestamp?, x: Float, y: Float) {
val entry = habit.computedEntries.get(timestamp!!)
if (habit.type == HabitType.NUMERICAL) {
val oldValue = entry.value.toDouble() / 1000
screen.showNumberPopup(oldValue, entry.notes) { newValue: Double, newNotes: String, x: Float, y: Float ->
screen.showNumberPopup(oldValue, entry.notes) { newValue: Double, newNotes: String ->
val value = (newValue * 1000).roundToInt()
if (newValue != oldValue) {
if (
@@ -72,7 +72,7 @@ open class ListHabitsBehavior @Inject constructor(
entry.value,
entry.notes,
habit.color
) { newValue: Int, newNotes: String, x: Float, y: Float ->
) { newValue: Int, newNotes: String ->
if (newValue != entry.value && newValue == YES_MANUAL) screen.showConfetti(habit.color, x, y)
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, newValue, newNotes))
}
@@ -159,9 +159,7 @@ open class ListHabitsBehavior @Inject constructor(
fun interface NumberPickerCallback {
fun onNumberPicked(
newValue: Double,
notes: String,
x: Float,
y: Float
notes: String
)
fun onNumberPickerDismissed() {}
}
@@ -169,9 +167,7 @@ open class ListHabitsBehavior @Inject constructor(
fun interface CheckMarkDialogCallback {
fun onNotesSaved(
value: Int,
notes: String,
x: Float,
y: Float
notes: String
)
fun onNotesDismissed() {}
}

View File

@@ -98,7 +98,7 @@ class HistoryCardPresenter(
entry.value,
entry.notes,
habit.color
) { newValue, newNotes, _: Float, _: Float ->
) { newValue, newNotes ->
commandRunner.run(
CreateRepetitionCommand(
habitList,
@@ -135,7 +135,7 @@ class HistoryCardPresenter(
screen.showNumberPopup(
value = oldValue / 1000.0,
notes = entry.notes
) { newValue: Double, newNotes: String, _: Float, _: Float ->
) { newValue: Double, newNotes: String ->
val thousands = (newValue * 1000).roundToInt()
commandRunner.run(
CreateRepetitionCommand(

View File

@@ -78,13 +78,13 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
@Test
fun testOnEdit() {
behavior.onEdit(habit2, getToday())
behavior.onEdit(habit2, getToday(), 0f, 0f)
verify(screen).showNumberPopup(
eq(0.1),
eq(""),
picker.capture()
)
picker.lastValue.onNumberPicked(100.0, "", 0f, 0f)
picker.lastValue.onNumberPicked(100.0, "")
val today = getTodayWithOffset()
assertThat(habit2.computedEntries.get(today).value, equalTo(100000))
}