From 897b1892e54fcf1d85e89917063a723513f5d459 Mon Sep 17 00:00:00 2001
From: Geoffroy Bonneville <24917789+wismna@users.noreply.github.com>
Date: Thu, 16 Jul 2026 13:53:51 -0400
Subject: [PATCH] Update SDK, Gradle and packages Fix timezone issues Add
timezone tests
---
.idea/caches/deviceStreaming.xml | 1228 +++++++++++++++--
.idea/deploymentTargetSelector.xml | 1 +
.idea/gradle.xml | 1 -
build.gradle | 10 +-
donext/build.gradle | 12 +-
donextv2/build.gradle.kts | 24 +-
.../donext/data/injection/ClockModule.kt | 17 +
.../domain/usecase/GetDueTodayTasksUseCase.kt | 16 +-
.../usecase/GetTaskListsWithOverdueUseCase.kt | 15 +-
.../usecase/GetDueTodayTasksUseCaseTest.kt | 80 ++
.../GetTaskListsWithOverdueUseCaseTest.kt | 50 +
gradle.properties | 10 +
gradle/gradle-daemon-jvm.properties | 13 +
gradle/wrapper/gradle-wrapper.properties | 2 +-
settings.gradle | 3 +
15 files changed, 1335 insertions(+), 147 deletions(-)
create mode 100644 donextv2/src/main/java/com/wismna/geoffroy/donext/data/injection/ClockModule.kt
create mode 100644 donextv2/src/test/java/com/wismna/geoffroy/donext/domain/usecase/GetDueTodayTasksUseCaseTest.kt
create mode 100644 donextv2/src/test/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCaseTest.kt
create mode 100644 gradle/gradle-daemon-jvm.properties
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
index e5f298b..e70eb94 100644
--- a/.idea/caches/deviceStreaming.xml
+++ b/.idea/caches/deviceStreaming.xml
@@ -10,7 +10,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
@@ -46,19 +46,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -70,11 +58,71 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -88,17 +136,64 @@
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -112,6 +207,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -119,7 +238,31 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -131,7 +274,7 @@
-
+
@@ -143,7 +286,31 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -155,7 +322,7 @@
-
+
@@ -167,7 +334,79 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -179,7 +418,31 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -191,7 +454,43 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -203,7 +502,7 @@
-
+
@@ -244,6 +543,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -268,6 +579,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -275,15 +634,28 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -291,6 +663,11 @@
+
@@ -305,66 +682,124 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -373,7 +808,31 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -385,7 +844,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -418,7 +889,6 @@
-
@@ -427,6 +897,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -439,6 +957,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -475,6 +1029,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -499,6 +1065,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -515,6 +1093,7 @@
+
@@ -522,6 +1101,11 @@
+
+
+
+
+
@@ -530,11 +1114,47 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -542,11 +1162,24 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -554,7 +1187,7 @@
-
+
@@ -566,11 +1199,23 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -616,11 +1261,35 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -633,6 +1302,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -681,6 +1362,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -717,6 +1410,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -745,6 +1462,7 @@
+
@@ -752,6 +1470,11 @@
+
+
+
+
+
@@ -760,7 +1483,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -777,6 +1512,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -785,9 +1568,9 @@
-
-
-
+
+
+
@@ -813,6 +1596,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -825,6 +1620,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -837,6 +1644,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -858,15 +1737,76 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -874,6 +1814,11 @@
+
+
+
+
+
@@ -886,6 +1831,11 @@
+
+
+
+
+
@@ -899,6 +1849,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -906,7 +1868,19 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -924,6 +1898,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -940,7 +1927,6 @@
-
@@ -953,7 +1939,6 @@
-
@@ -966,7 +1951,6 @@
-
@@ -982,11 +1966,35 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 7ca90ea..5ac072a 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,7 @@
+
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 3e94301..bed333d 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -6,7 +6,6 @@
-
diff --git a/build.gradle b/build.gradle
index 7608a10..97ec266 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,9 +13,9 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.13.0'
- classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21'
- classpath 'org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.0.21'
+ classpath 'com.android.tools.build:gradle:9.3.0'
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10'
+ classpath 'org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.2.10'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -23,8 +23,8 @@ buildscript {
}
plugins {
- id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
- id("com.google.dagger.hilt.android") version "2.57.2" apply false
+ id("com.google.devtools.ksp") version '2.3.2' apply false
+ id("com.google.dagger.hilt.android") version "2.60.1" apply false
}
allprojects {
diff --git a/donext/build.gradle b/donext/build.gradle
index 740c349..0e430f3 100644
--- a/donext/build.gradle
+++ b/donext/build.gradle
@@ -1,7 +1,7 @@
apply plugin: 'com.android.application'
android {
- compileSdk 36
+ compileSdk 37
defaultConfig {
applicationId "com.wismna.geoffroy.donext"
@@ -13,7 +13,7 @@ android {
buildTypes {
release {
minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
namespace 'com.wismna.geoffroy.donext'
@@ -26,12 +26,12 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.7.1'
- implementation 'com.google.android.material:material:1.13.0'
+ implementation 'com.google.android.material:material:1.14.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.percentlayout:percentlayout:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
- implementation 'androidx.browser:browser:1.9.0'
- implementation 'net.danlew:android.joda:2.13.1'
+ implementation 'androidx.browser:browser:1.10.0'
+ implementation 'net.danlew:android.joda:2.14.2'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.preference:preference:1.2.1'
testImplementation 'junit:junit:4.13.2'
@@ -42,7 +42,7 @@ dependencies {
implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
// Lifecycle components
- def lifecycleVersion = '2.9.4'
+ def lifecycleVersion = '2.11.0'
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
diff --git a/donextv2/build.gradle.kts b/donextv2/build.gradle.kts
index 67b6bdf..4e1b8ae 100644
--- a/donextv2/build.gradle.kts
+++ b/donextv2/build.gradle.kts
@@ -14,7 +14,7 @@ ksp {
android {
namespace = "com.wismna.geoffroy.donext"
- compileSdk = 36
+ compileSdk = 37
defaultConfig {
applicationId = "com.wismna.geoffroy.donext"
@@ -82,35 +82,35 @@ android {
}
dependencies {
- implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.4")
- implementation("androidx.activity:activity-compose:1.11.0")
- implementation(platform("androidx.compose:compose-bom:2025.11.00"))
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.11.0")
+ implementation("androidx.activity:activity-compose:1.13.0")
+ implementation(platform("androidx.compose:compose-bom:2026.06.01"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material3:material3-window-size-class:1.4.0")
implementation("androidx.compose.material:material-icons-extended:1.7.8")
- implementation("androidx.navigation:navigation-compose:2.9.6")
- implementation("androidx.hilt:hilt-navigation-compose:1.3.0")
- implementation("sh.calvin.reorderable:reorderable:3.0.0")
+ implementation("androidx.navigation:navigation-compose:2.9.8")
+ implementation("androidx.hilt:hilt-navigation-compose:1.4.0")
+ implementation("sh.calvin.reorderable:reorderable:3.1.0")
testImplementation("junit:junit:4.13.2")
- testImplementation("io.mockk:mockk:1.14.6")
- testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
+ testImplementation("io.mockk:mockk:1.14.11")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0")
testImplementation("com.google.truth:truth:1.4.5")
androidTestImplementation("androidx.test.ext:junit-ktx:1.3.0")
- androidTestImplementation(platform("androidx.compose:compose-bom:2025.11.00"))
+ androidTestImplementation(platform("androidx.compose:compose-bom:2026.06.01"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
androidTestImplementation("com.google.truth:truth:1.4.5")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
- val roomVersion = "2.8.3"
+ val roomVersion = "2.8.4"
implementation("androidx.room:room-runtime:$roomVersion")
androidTestImplementation("androidx.room:room-testing:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
- val hiltVersion = "2.57.2"
+ val hiltVersion = "2.60.1"
implementation("com.google.dagger:hilt-android:$hiltVersion")
ksp("com.google.dagger:hilt-android-compiler:$hiltVersion")
}
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/data/injection/ClockModule.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/injection/ClockModule.kt
new file mode 100644
index 0000000..77adcdb
--- /dev/null
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/data/injection/ClockModule.kt
@@ -0,0 +1,17 @@
+package com.wismna.geoffroy.donext.data.injection
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import java.time.Clock
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object ClockModule {
+
+ @Provides
+ @Singleton
+ fun provideClock(): Clock = Clock.systemDefaultZone()
+}
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetDueTodayTasksUseCase.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetDueTodayTasksUseCase.kt
index 290e899..a686fef 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetDueTodayTasksUseCase.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetDueTodayTasksUseCase.kt
@@ -3,20 +3,24 @@ package com.wismna.geoffroy.donext.domain.usecase
import com.wismna.geoffroy.donext.domain.model.Task
import com.wismna.geoffroy.donext.domain.repository.TaskRepository
import kotlinx.coroutines.flow.Flow
+import java.time.Clock
import java.time.LocalDate
-import java.time.ZoneOffset
import javax.inject.Inject
-class GetDueTodayTasksUseCase @Inject constructor(private val repository: TaskRepository) {
+class GetDueTodayTasksUseCase @Inject constructor(
+ private val repository: TaskRepository,
+ private val clock: Clock
+) {
operator fun invoke(): Flow> {
- val todayStart = LocalDate.now()
- .atStartOfDay(ZoneOffset.UTC)
+ val today = LocalDate.now(clock)
+ val todayStart = today
+ .atStartOfDay(clock.zone)
.toInstant()
.toEpochMilli()
- val todayEnd = LocalDate.now()
+ val todayEnd = today
.plusDays(1)
- .atStartOfDay(ZoneOffset.UTC)
+ .atStartOfDay(clock.zone)
.toInstant()
.toEpochMilli() - 1
return repository.getDueTodayTasks(
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCase.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCase.kt
index 731834a..1a84dc3 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCase.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCase.kt
@@ -3,17 +3,20 @@ package com.wismna.geoffroy.donext.domain.usecase
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
import com.wismna.geoffroy.donext.domain.repository.TaskRepository
import kotlinx.coroutines.flow.Flow
+import java.time.Clock
import java.time.LocalDate
-import java.time.ZoneOffset
import javax.inject.Inject
-class GetTaskListsWithOverdueUseCase @Inject constructor(private val taskRepository: TaskRepository) {
+class GetTaskListsWithOverdueUseCase @Inject constructor(
+ private val taskRepository: TaskRepository,
+ private val clock: Clock
+) {
operator fun invoke(): Flow> {
return taskRepository.getTaskListsWithOverdue(
- LocalDate.now()
- .atStartOfDay(ZoneOffset.UTC)
- .toInstant()
- .toEpochMilli()
+ LocalDate.now(clock)
+ .atStartOfDay(clock.zone)
+ .toInstant()
+ .toEpochMilli()
)
}
}
\ No newline at end of file
diff --git a/donextv2/src/test/java/com/wismna/geoffroy/donext/domain/usecase/GetDueTodayTasksUseCaseTest.kt b/donextv2/src/test/java/com/wismna/geoffroy/donext/domain/usecase/GetDueTodayTasksUseCaseTest.kt
new file mode 100644
index 0000000..2200c6b
--- /dev/null
+++ b/donextv2/src/test/java/com/wismna/geoffroy/donext/domain/usecase/GetDueTodayTasksUseCaseTest.kt
@@ -0,0 +1,80 @@
+package com.wismna.geoffroy.donext.domain.usecase
+
+import com.google.common.truth.Truth.assertThat
+import com.wismna.geoffroy.donext.domain.model.Priority
+import com.wismna.geoffroy.donext.domain.model.Task
+import com.wismna.geoffroy.donext.domain.repository.TaskRepository
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import java.time.Clock
+import java.time.LocalDate
+import java.time.ZoneId
+
+class GetDueTodayTasksUseCaseTest {
+
+ @Test
+ fun `computes today's window using the clock's own zone, not UTC`() {
+ // Paris in summer is UTC+2: local midnight for this date is 22:00 UTC
+ // the *previous* day. A window anchored to UTC midnight would miss it.
+ val zone = ZoneId.of("Europe/Paris")
+ val today = LocalDate.of(2026, 7, 16)
+ val clock = Clock.fixed(today.atStartOfDay(zone).plusHours(10).toInstant(), zone)
+
+ val repository = mockk()
+ every { repository.getDueTodayTasks(any(), any()) } returns flowOf(emptyList())
+
+ GetDueTodayTasksUseCase(repository, clock).invoke()
+
+ val expectedStart = today.atStartOfDay(zone).toInstant().toEpochMilli()
+ val expectedEnd = today.plusDays(1).atStartOfDay(zone).toInstant().toEpochMilli() - 1
+
+ verify { repository.getDueTodayTasks(expectedStart, expectedEnd) }
+ }
+
+ @Test
+ fun `window covers exactly local midnight to the millisecond before the next local midnight`() {
+ val zone = ZoneId.of("America/Montreal")
+ val today = LocalDate.of(2026, 1, 5)
+ val clock = Clock.fixed(today.atStartOfDay(zone).plusHours(6).toInstant(), zone)
+
+ val repository = mockk()
+ every { repository.getDueTodayTasks(any(), any()) } returns flowOf(emptyList())
+
+ GetDueTodayTasksUseCase(repository, clock).invoke()
+
+ val expectedStart = today.atStartOfDay(zone).toInstant().toEpochMilli()
+ val expectedEnd = today.plusDays(1).atStartOfDay(zone).toInstant().toEpochMilli() - 1
+ assertThat(expectedEnd - expectedStart).isEqualTo(24L * 60 * 60 * 1000 - 1)
+
+ verify { repository.getDueTodayTasks(expectedStart, expectedEnd) }
+ }
+
+ @Test
+ fun `passes through the tasks emitted by the repository`() = runTest {
+ val zone = ZoneId.systemDefault()
+ val clock = Clock.fixed(LocalDate.of(2026, 1, 1).atStartOfDay(zone).toInstant(), zone)
+ val tasks = listOf(
+ Task(
+ id = 1L,
+ taskListId = 1L,
+ name = "Task",
+ description = "",
+ priority = Priority.NORMAL,
+ isDone = false,
+ isDeleted = false
+ )
+ )
+
+ val repository = mockk()
+ every { repository.getDueTodayTasks(any(), any()) } returns flowOf(tasks)
+
+ val result = GetDueTodayTasksUseCase(repository, clock).invoke().first()
+
+ assertThat(result).isEqualTo(tasks)
+ }
+}
diff --git a/donextv2/src/test/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCaseTest.kt b/donextv2/src/test/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCaseTest.kt
new file mode 100644
index 0000000..4de20ed
--- /dev/null
+++ b/donextv2/src/test/java/com/wismna/geoffroy/donext/domain/usecase/GetTaskListsWithOverdueUseCaseTest.kt
@@ -0,0 +1,50 @@
+package com.wismna.geoffroy.donext.domain.usecase
+
+import com.google.common.truth.Truth.assertThat
+import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
+import com.wismna.geoffroy.donext.domain.repository.TaskRepository
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import java.time.Clock
+import java.time.LocalDate
+import java.time.ZoneId
+
+class GetTaskListsWithOverdueUseCaseTest {
+
+ @Test
+ fun `computes the overdue cutoff at local midnight, not UTC midnight`() {
+ // Paris in summer is UTC+2: local midnight for this date is 22:00 UTC
+ // the *previous* day. A cutoff anchored to UTC midnight would flag
+ // today's tasks as overdue a full day too early.
+ val zone = ZoneId.of("Europe/Paris")
+ val today = LocalDate.of(2026, 7, 16)
+ val clock = Clock.fixed(today.atStartOfDay(zone).plusHours(10).toInstant(), zone)
+
+ val repository = mockk()
+ every { repository.getTaskListsWithOverdue(any()) } returns flowOf(emptyList())
+
+ GetTaskListsWithOverdueUseCase(repository, clock).invoke()
+
+ val expectedCutoff = today.atStartOfDay(zone).toInstant().toEpochMilli()
+ verify { repository.getTaskListsWithOverdue(expectedCutoff) }
+ }
+
+ @Test
+ fun `passes through the lists emitted by the repository`() = runTest {
+ val zone = ZoneId.systemDefault()
+ val clock = Clock.fixed(LocalDate.of(2026, 1, 1).atStartOfDay(zone).toInstant(), zone)
+ val lists = listOf(TaskListWithOverdue(id = 1L, name = "Work", overdueCount = 2))
+
+ val repository = mockk()
+ every { repository.getTaskListsWithOverdue(any()) } returns flowOf(lists)
+
+ val result = GetTaskListsWithOverdueUseCase(repository, clock).invoke().first()
+
+ assertThat(result).isEqualTo(lists)
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index 147756b..1040884 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,7 +16,17 @@
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
+android.builtInKotlin=false
+android.defaults.buildfeatures.resvalues=true
+android.dependency.useConstraints=true
+android.enableAppCompileTimeRClass=false
android.enableJetifier=false
+android.newDsl=false
+android.r8.optimizedResourceShrinking=false
+android.r8.strictFullModeForKeepRules=false
+android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
+android.uniquePackageNames=false
android.useAndroidX=true
+android.usesSdkInManifest.disallowed=false
org.gradle.configuration-cache=true
org.gradle.jvmargs=-Xmx4g
\ No newline at end of file
diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties
new file mode 100644
index 0000000..baa28d1
--- /dev/null
+++ b/gradle/gradle-daemon-jvm.properties
@@ -0,0 +1,13 @@
+#This file is generated by updateDaemonJvm
+toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
+toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
+toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
+toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
+toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/7083b89563e7ce20943037b8cd2b8cc2/redirect
+toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/060bbb778a1f55ea705fdebd2ccfeab9/redirect
+toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/491f83666ae7f4d6ebb28fee72ebb035/redirect
+toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/0d1a1acdc708062093673f65aa9aba4b/redirect
+toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d09679dc60fe5aa05ef7d03efdefac20/redirect
+toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/ed4e3bf2f5e7c5d9aabc4cbd8acd555e/redirect
+toolchainVendor=JETBRAINS
+toolchainVersion=21
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3ae1e2f..efb903f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/settings.gradle b/settings.gradle
index 61f7534..1cfe3f5 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,5 @@
+plugins {
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.10.0'
+}
include ':donext'
include ':donextv2'