diff --git a/.github/workflows/ci_checks.yml b/.github/workflows/ci_checks.yml index 11a050e6a10..36585e88e76 100644 --- a/.github/workflows/ci_checks.yml +++ b/.github/workflows/ci_checks.yml @@ -139,7 +139,7 @@ jobs: force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - disk-size: 8G + disk-size: 6G script: | adb root mkdir -p app/build/outputs/test-artifacts diff --git a/CHANGELOG.md b/CHANGELOG.md index f951e07d548..cd24bd63659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,12 @@ ### Internal -- Bump Sentry to v8.29.0 +- Bump Sentry to v8.30.0 - Bump AndroidX Lifecycle to v2.10.0 - Bump AndroidX Test Runner to v1.7.0 - Bump AndroidX Test Rules to v1.7.0 - Bump AndroidX JUnit to v1.3.0 -- Bump Jackson Core to v2.20.1 +- Bump Jackson Core to v2.21.0 - Bump Play Services Auth to v21.5.0 - Bump Material Components to v1.12.0 - Bump Kotlin to v2.3.0 @@ -23,18 +23,18 @@ - Bump AndroidX Benchmark to v1.4.1 - Bump AndroidX Camera to v1.5.2 - Bump AndroidX Camera View to v1.5.2 -- Bump dagger to v2.57.2 +- Bump dagger to v2.58 - Bump UUID generator to v5.2.0 -- Bump Compose BOM to v2025.12.01 +- Bump Compose BOM to v2026.01.00 - Bump Sentry Android to v5.12.2 - Update GH Actions `checkout` to v6 - Update GH Actions `setup-jdk` to to v5 - Bump Gradle to v9.2.1 -- Bump Mockito Kotlin to v6.1.0 +- Bump Mockito Kotlin to v6.2.1 - Add helper function `diagnosedAt` to update the htn and dm diagnosed at fields in medical history - Bump asm to v9.9.1 -- Bump AGP to v8.13.2 -- Bump Lint to v31.13.2 +- Bump AGP to v9.0.0 +- Bump Lint to v32.0.0 - Disable Ksp1 and enable Ksp2 by default - Update GH Workflow Actions `Setup Gradle` to v5 - Bump Google Service to v4.4.4 @@ -44,6 +44,10 @@ - Bump sqlcipher to v4.12.0 - Bump GH Actions `cache` to v5 - Bump GH Actions `upload-artifacts` to v6 +- Add support for Android 16 + - Bump compile and target SDK to 36 +- Bump AndroidX Activity to v1.12.2 +- Bump Chucker to v4.3.0 ### Changes @@ -88,6 +92,7 @@ - Migrate new medical history screen to Jetpack Compose - Bump AndroidX Benchmark to v1.4.0 - Bump Open CSV to v5.12.0 +- Bump AndroidX Core KTX to v1.17.0 ### Changes diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d8e3688e8e1..9b51f3f1b2c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,7 +8,6 @@ import java.util.EnumSet plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.sentry) alias(libs.plugins.ksp) diff --git a/app/src/main/java/org/simple/clinic/main/TheActivity.kt b/app/src/main/java/org/simple/clinic/main/TheActivity.kt index a3b53b0145d..bd169e6e8d0 100644 --- a/app/src/main/java/org/simple/clinic/main/TheActivity.kt +++ b/app/src/main/java/org/simple/clinic/main/TheActivity.kt @@ -6,7 +6,6 @@ import android.content.Intent import android.content.res.Configuration import android.graphics.Color import android.os.Bundle -import android.view.WindowManager import androidx.activity.SystemBarStyle import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity @@ -15,8 +14,8 @@ import androidx.work.WorkManager import io.github.inflationx.viewpump.ViewPumpContextWrapper import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable -import org.simple.clinic.BuildConfig import org.simple.clinic.ClinicApp +import org.simple.clinic.activity.permissions.ActivityPermissionResult import org.simple.clinic.deeplink.DeepLinkResult import org.simple.clinic.deeplink.OpenPatientSummary import org.simple.clinic.deeplink.OpenPatientSummaryWithTeleconsultLog @@ -31,15 +30,14 @@ import org.simple.clinic.home.patients.LoggedOutOnOtherDeviceDialog import org.simple.clinic.login.applock.AppLockConfig import org.simple.clinic.login.applock.AppLockScreenKey import org.simple.clinic.mobius.MobiusDelegate +import org.simple.clinic.navigation.v2.ActivityResult import org.simple.clinic.navigation.v2.History import org.simple.clinic.navigation.v2.Router +import org.simple.clinic.navigation.v2.ScreenResultBus import org.simple.clinic.navigation.v2.compat.wrap import org.simple.clinic.registerorlogin.AuthenticationActivity import org.simple.clinic.remoteconfig.UpdateRemoteConfigWorker import org.simple.clinic.remoteconfig.UpdateRemoteConfigWorker.Companion.REMOTE_CONFIG_SYNC_WORKER -import org.simple.clinic.navigation.v2.ScreenResultBus -import org.simple.clinic.activity.permissions.ActivityPermissionResult -import org.simple.clinic.navigation.v2.ActivityResult import org.simple.clinic.storage.MemoryValue import org.simple.clinic.sync.DataSync import org.simple.clinic.sync.SyncSetup @@ -48,6 +46,7 @@ import org.simple.clinic.user.UserSession import org.simple.clinic.util.UtcClock import org.simple.clinic.util.disableAnimations import org.simple.clinic.util.finishWithoutAnimations +import org.simple.clinic.util.handleBackPress import org.simple.clinic.util.popWithParcelableResult import org.simple.clinic.util.unsafeLazy import org.simple.clinic.util.withLocale @@ -192,6 +191,14 @@ class TheActivity : AppCompatActivity(), TheActivityUi { navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT) ) super.onCreate(savedInstanceState) + + handleBackPress { + if (!router.onBackPressed()) { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + } + router.onReady(savedInstanceState) delegate.onRestoreInstanceState(savedInstanceState) } @@ -199,11 +206,6 @@ class TheActivity : AppCompatActivity(), TheActivityUi { @SuppressLint("CheckResult") override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) - @Suppress("ConstantConditionIf") - if (BuildConfig.DISABLE_SCREENSHOT) { - window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) - } - if (savedInstanceState == null) { disposables.addAll( syncSetup.run(), @@ -270,12 +272,6 @@ class TheActivity : AppCompatActivity(), TheActivityUi { screenResults.send(ActivityPermissionResult(requestCode)) } - override fun onBackPressed() { - if (!router.onBackPressed()) { - super.onBackPressed() - } - } - override fun onSaveInstanceState(outState: Bundle) { router.onSaveInstanceState(outState) delegate.onSaveInstanceState(outState) diff --git a/app/src/main/java/org/simple/clinic/registerorlogin/AuthenticationActivity.kt b/app/src/main/java/org/simple/clinic/registerorlogin/AuthenticationActivity.kt index 8847f08ce2f..ac9d3bfea47 100644 --- a/app/src/main/java/org/simple/clinic/registerorlogin/AuthenticationActivity.kt +++ b/app/src/main/java/org/simple/clinic/registerorlogin/AuthenticationActivity.kt @@ -11,17 +11,18 @@ import androidx.appcompat.app.AppCompatActivity import io.github.inflationx.viewpump.ViewPumpContextWrapper import io.reactivex.Observable import org.simple.clinic.ClinicApp +import org.simple.clinic.activity.permissions.ActivityPermissionResult import org.simple.clinic.di.InjectorProviderContextWrapper import org.simple.clinic.empty.EmptyScreenKey import org.simple.clinic.feature.Features import org.simple.clinic.mobius.MobiusDelegate +import org.simple.clinic.navigation.v2.ActivityResult import org.simple.clinic.navigation.v2.Router +import org.simple.clinic.navigation.v2.ScreenResultBus import org.simple.clinic.navigation.v2.compat.wrap import org.simple.clinic.registration.phone.RegistrationPhoneScreenKey -import org.simple.clinic.navigation.v2.ScreenResultBus -import org.simple.clinic.activity.permissions.ActivityPermissionResult -import org.simple.clinic.navigation.v2.ActivityResult import org.simple.clinic.selectcountry.SelectCountryScreen +import org.simple.clinic.util.handleBackPress import org.simple.clinic.util.unsafeLazy import org.simple.clinic.util.withLocale import org.simple.clinic.util.wrap @@ -86,6 +87,14 @@ class AuthenticationActivity : AppCompatActivity(), AuthenticationUiActions { navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT) ) super.onCreate(savedInstanceState) + + handleBackPress { + if (!router.onBackPressed()) { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } + } + router.onReady(savedInstanceState) delegate.onRestoreInstanceState(savedInstanceState) } @@ -141,12 +150,6 @@ class AuthenticationActivity : AppCompatActivity(), AuthenticationUiActions { screenResults.send(ActivityPermissionResult(requestCode)) } - override fun onBackPressed() { - if (!router.onBackPressed()) { - super.onBackPressed() - } - } - override fun onSaveInstanceState(outState: Bundle) { router.onSaveInstanceState(outState) delegate.onSaveInstanceState(outState) diff --git a/app/src/main/java/org/simple/clinic/setup/SetupActivity.kt b/app/src/main/java/org/simple/clinic/setup/SetupActivity.kt index 57bbd278b7e..3bbdc7bc944 100644 --- a/app/src/main/java/org/simple/clinic/setup/SetupActivity.kt +++ b/app/src/main/java/org/simple/clinic/setup/SetupActivity.kt @@ -5,14 +5,12 @@ import android.content.Intent import android.content.res.Configuration import android.graphics.Color import android.os.Bundle -import android.view.WindowManager import androidx.activity.SystemBarStyle import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.github.inflationx.viewpump.ViewPumpContextWrapper import io.reactivex.Observable -import org.simple.clinic.BuildConfig import org.simple.clinic.ClinicApp import org.simple.clinic.R import org.simple.clinic.activity.permissions.ActivityPermissionResult @@ -32,6 +30,7 @@ import org.simple.clinic.util.UtcClock import org.simple.clinic.util.disableAnimations import org.simple.clinic.util.disablePendingTransitions import org.simple.clinic.util.finishWithoutAnimations +import org.simple.clinic.util.handleBackPress import org.simple.clinic.util.unsafeLazy import org.simple.clinic.util.withLocale import org.simple.clinic.util.wrap @@ -86,9 +85,12 @@ class SetupActivity : AppCompatActivity(), UiActions { navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT) ) super.onCreate(savedInstanceState) - @Suppress("ConstantConditionIf") - if (BuildConfig.DISABLE_SCREENSHOT) { - window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) + + handleBackPress { + if (!router.onBackPressed()) { + isEnabled = false + onBackPressedDispatcher.onBackPressed() + } } binding = ActivitySetupBinding.inflate(layoutInflater) @@ -114,12 +116,6 @@ class SetupActivity : AppCompatActivity(), UiActions { super.onSaveInstanceState(outState) } - override fun onBackPressed() { - if (!router.onBackPressed()) { - super.onBackPressed() - } - } - override fun attachBaseContext(baseContext: Context) { setupDiGraph() diff --git a/app/src/main/java/org/simple/clinic/util/AndroidExtensions.kt b/app/src/main/java/org/simple/clinic/util/AndroidExtensions.kt index c7d74564ac9..ec8b4c58401 100644 --- a/app/src/main/java/org/simple/clinic/util/AndroidExtensions.kt +++ b/app/src/main/java/org/simple/clinic/util/AndroidExtensions.kt @@ -6,9 +6,11 @@ import android.content.Context import android.content.Intent import android.content.res.Configuration import android.view.KeyEvent +import androidx.activity.OnBackPressedCallback import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.annotation.ColorRes +import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import io.reactivex.Observable import org.simple.clinic.feature.Feature @@ -112,3 +114,19 @@ fun Context.resolveFloat(attrRes: Int, fallback: (() -> Float)? = null): Float { a.recycle() } } + +/** + * A reusable extension function to handle a custom back press action. + * The `onBackPressedAction` lambda tells the function what to do + * when the back button is pressed. + */ +fun AppCompatActivity.handleBackPress( + onBackPressedAction: OnBackPressedCallback.() -> Unit +) { + val onBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + this.onBackPressedAction() + } + } + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) +} diff --git a/app/src/main/java/org/simple/clinic/widgets/BottomSheetActivity.kt b/app/src/main/java/org/simple/clinic/widgets/BottomSheetActivity.kt index e7ae3b0c688..44b5cfa1373 100644 --- a/app/src/main/java/org/simple/clinic/widgets/BottomSheetActivity.kt +++ b/app/src/main/java/org/simple/clinic/widgets/BottomSheetActivity.kt @@ -4,11 +4,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity -import org.simple.clinic.BuildConfig import org.simple.clinic.databinding.BottomSheetBinding import org.simple.clinic.util.disablePendingTransitions +import org.simple.clinic.util.handleBackPress /** * We're using Activities as fake bottom sheets instead of BottomSheetDialog because we want @@ -31,14 +30,13 @@ abstract class BottomSheetActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { disablePendingTransitions() super.onCreate(savedInstanceState) - @Suppress("ConstantConditionIf") - if (BuildConfig.DISABLE_SCREENSHOT) { - window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) - } - bottomSheetBinding = BottomSheetBinding.inflate(layoutInflater) super.setContentView(bottomSheetBinding.root) + handleBackPress { + finish() + } + contentContainer.setOnClickListener { // Swallow clicks to avoid dismissing the sheet accidentally. } @@ -77,13 +75,6 @@ abstract class BottomSheetActivity : AppCompatActivity() { } open fun onBackgroundClick() { - onBackPressed() - } - - @Deprecated("Deprecated in Java") - override fun onBackPressed() { - super.onBackPressed() - // Routing to finish() just so that the exit animation can be played. - finish() + onBackPressedDispatcher.onBackPressed() } } diff --git a/app/src/test/java/org/simple/clinic/mobius/CustomFirstMatchers.kt b/app/src/test/java/org/simple/clinic/mobius/CustomFirstMatchers.kt deleted file mode 100644 index a41606a7c68..00000000000 --- a/app/src/test/java/org/simple/clinic/mobius/CustomFirstMatchers.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.simple.clinic.mobius - -import com.spotify.mobius.First -import org.hamcrest.Description -import org.hamcrest.Matcher -import org.hamcrest.TypeSafeDiagnosingMatcher -import org.hamcrest.core.Is - -object CustomFirstMatchers { - fun doesNotHaveEffectOfType(type: Class<*>): Matcher> { - - return object : TypeSafeDiagnosingMatcher>() { - - private val typeMatcher = Is.isA>(type) - - override fun describeTo( - description: Description - ) { - description - .appendText("should not have effect matching: ") - .appendDescriptionOf(typeMatcher) - } - - override fun matchesSafely( - item: First, - mismatchDescription: Description - ): Boolean { - val itemsMatching = item - .effects() - .filter(typeMatcher::matches) - - return if (itemsMatching.isNotEmpty()) { - mismatchDescription - .appendText("found effects: ") - .appendValueList("[", ", ", "]", itemsMatching) - false - } else { - true - } - } - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts index e177aa648f7..795c5d941da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,6 @@ plugins { alias(libs.plugins.android.library) apply false alias(libs.plugins.android.lint) apply false alias(libs.plugins.androidx.benchmark) apply false - alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.kotlin.compose.compiler) apply false @@ -14,9 +13,9 @@ plugins { buildscript { extra.apply { - set("compileSdkVersion", 35) + set("compileSdkVersion", 36) set("minSdkVersion", 26) - set("targetSdkVersion", 35) + set("targetSdkVersion", 36) } repositories { diff --git a/common-ui/build.gradle.kts b/common-ui/build.gradle.kts index a278f67be6e..5822021f865 100644 --- a/common-ui/build.gradle.kts +++ b/common-ui/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose.compiler) } diff --git a/gradle.properties b/gradle.properties index 6cfdbfbd78d..eacd7a6dbb3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -47,3 +47,13 @@ disableScreenshot=false allowRootedDevice=true maestroTests=false org.gradle.unsafe.configuration-cache=true + +# AGP 9 compatibility flags (temporary) +# Remove gradually before AGP 10 +android.uniquePackageNames=false +android.enableAppCompileTimeRClass=false +android.r8.optimizedResourceShrinking=false +# remove this when sentry-android-gradle-plugin releases version 6.x.x +# https://github.com/getsentry/sentry-android-gradle-plugin/issues/1004 +android.newDsl=false + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 936ce55b80f..e9c1810659d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.13.2" +agp = "9.0.0" androidx-cameraView = "1.5.2" androidx-camera = "1.5.2" @@ -9,10 +9,10 @@ androidx-work = "2.11.0" androidx-security-crypto = "1.1.0" androidx-viewmodel = "2.10.0" androidx-lifecycle = "2.10.0" -androidx-activity = "1.10.1" +androidx-activity = "1.12.2" -chucker = "4.2.0" -dagger = "2.57.2" +chucker = "4.3.0" +dagger = "2.58" kotlin = "2.3.0" @@ -20,7 +20,7 @@ ksp = "2.3.4" ktlint = "0.36.0" -lint = "31.13.2" +lint = "32.0.0" mobius = "2.1.1" @@ -38,7 +38,7 @@ coroutines = "1.10.2" compose-compiler = "1.5.13" -androidx-compose-bom = "2025.12.01" +androidx-compose-bom = "2026.01.00" composeThemeAdapter = "0.36.0" @@ -56,7 +56,7 @@ androidx-appcompat = "androidx.appcompat:appcompat:1.7.1" androidx-archCoreTesting = "androidx.arch.core:core-testing:2.2.0" androidx-cardview = "androidx.cardview:cardview:1.0.0" androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.2.1" -androidx-core-ktx = "androidx.core:core-ktx:1.16.0" +androidx-core-ktx = "androidx.core:core-ktx:1.17.0" androidx-fragment = "androidx.fragment:fragment-ktx:1.8.9" androidx-preference = "androidx.preference:preference:1.2.1" androidx-recyclerview = "androidx.recyclerview:recyclerview:1.4.0" @@ -116,7 +116,7 @@ itemanimators = "com.mikepenz:itemanimators:1.1.0" itext7 = "com.itextpdf:itext7-core:7.2.5" -jackson-core = "com.fasterxml.jackson.core:jackson-core:2.20.1" +jackson-core = "com.fasterxml.jackson.core:jackson-core:2.21.0" jbcrypt = "org.mindrot:jbcrypt:0.4" @@ -142,7 +142,7 @@ lottie = "com.airbnb.android:lottie-compose:6.7.1" material = "com.google.android.material:material:1.13.0" -mockito-kotlin = "org.mockito.kotlin:mockito-kotlin:6.1.0" +mockito-kotlin = "org.mockito.kotlin:mockito-kotlin:6.2.1" mobius-android = { module = "com.spotify.mobius:mobius-android", version.ref = "mobius" } mobius-core = { module = "com.spotify.mobius:mobius-core", version.ref = "mobius" } @@ -179,7 +179,7 @@ rx-java = "io.reactivex.rxjava2:rxjava:2.2.21" rx-kotlin = "io.reactivex.rxjava2:rxkotlin:2.4.0" rx-preferences = "com.f2prateek.rx.preferences2:rx-preferences:2.0.1" -sentry-android = "io.sentry:sentry-android:8.29.0" +sentry-android = "io.sentry:sentry-android:8.30.0" signaturepad = "com.github.gcacace:signature-pad:1.3.1" @@ -224,7 +224,6 @@ android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } android-lint = { id = "com.android.lint", version.ref = "agp" } androidx-benchmark = { id = "androidx.benchmark", version = "1.4.1" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/mobius-base/build.gradle.kts b/mobius-base/build.gradle.kts index 51f2119cda7..cf5f4b887f8 100644 --- a/mobius-base/build.gradle.kts +++ b/mobius-base/build.gradle.kts @@ -1,7 +1,6 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.androidx.benchmark) - alias(libs.plugins.kotlin.android) } android { diff --git a/simple-platform/build.gradle.kts b/simple-platform/build.gradle.kts index b0cdc6e0a97..f4d536d4eab 100644 --- a/simple-platform/build.gradle.kts +++ b/simple-platform/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) } android { diff --git a/simple-platform/consumer-rules.pro b/simple-platform/consumer-rules.pro new file mode 100644 index 00000000000..e69de29bb2d diff --git a/simple-visuals/build.gradle.kts b/simple-visuals/build.gradle.kts index 7cb9bcf55f3..64214be5af5 100644 --- a/simple-visuals/build.gradle.kts +++ b/simple-visuals/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) } android { diff --git a/simple-visuals/consumer-rules.pro b/simple-visuals/consumer-rules.pro new file mode 100644 index 00000000000..e69de29bb2d