diff --git a/android/README.md b/android/README.md index bd0eb17..51c7311 100644 --- a/android/README.md +++ b/android/README.md @@ -11,18 +11,18 @@ compile, unit tests (including every vector of `contracts/test-vectors/`), lint |---|---|---| | `:core:model` | `kvitt.jvm.library` | UI contract (§14.2.4), domain types `model.money` (§14.5.1), analytics types `model.analytics` (§14.5.4) | | `:domain` | `kvitt.jvm.library` | §14.5 functions; `VectorTest` runs every vector file, copied by `:domain:copyTestVectors` | -| `:core:design` | `kvitt.android.library` + `kvitt.android.compose` | `KvittTheme`, tokens, shared components, `Canvas` charts, formatters and shared strings (M6, `3080804`; fixes M6b, `3a1bd14`; SPEC §6.2); M6 owns the module | -| `:core:fakes` | `kvitt.android.library` | `FakeRepositories(scenario)`, `FakeScenarios.empty`; scenarios of feature X go in `fakes//` (added by M7a to M7f) | -| `:data` | `kvitt.android.library` + KSP + Room 3 | Room entities and `KvittDatabase` v1, schema in `data/schemas/`; M5 owns the module | -| `:adapter:fcm` | `kvitt.android.library` | shell; M8 owns it | -| `:feature:*` | `kvitt.android.feature` | screens built by M7a to M7f (landed 2026-10-18), goldens in `feature//src/test/screenshots/` (full screen 411 × 891 dp at 1x, D-098); top-level screens draw their own `KvittNavigationBar` (SPEC §13.2, D-071); M7g does the cleanups after M6b | -| `:app` | `kvitt.android.application` + compose + hilt + Roborazzi | Application, MainActivity, goldens in `app/src/test/screenshots/`; release binds `:data` (`DataBindingsModule`, `e6ad858`), debug binds the fakes in `src/debug/.../DebugFakesModule.kt` (`72f21b2`) until M8 deletes it; M8 owns the module | +| `:core:design` | `kvitt.android.library` + `kvitt.android.compose` + `kvitt.android.screenshots` | `KvittTheme`, tokens, shared components, `Canvas` charts, formatters, shared failure texts and limits, shared strings (SPEC §6.2); its test fixtures are the shared test support (below) | +| `:core:fakes` | `kvitt.android.library` | `FakeRepositories(scenario)`, `FakeScenarios.empty`; scenarios of feature X go in `fakes//` | +| `:data` | `kvitt.android.library` + `kvitt.android.hilt` + KSP + Room 3 | Room entities and `KvittDatabase`, schema in `data/schemas/`, Ktor client, sync engine, repositories, updater | +| `:adapter:fcm` | `kvitt.android.library` + `kvitt.android.hilt` | Firebase Cloud Messaging: the service, token calls and the mapping to `PushEvent` | +| `:feature:*` | `kvitt.android.feature` | the screens; goldens in `feature//src/test/screenshots/` (full screen 411 × 891 dp at 1x, D-098); top-level screens draw their own `KvittNavigationBar` (SPEC §13.2, D-071) | +| `:app` | `kvitt.android.application` + compose + hilt + screenshots | Application, MainActivity, `NavHost`, push and notifications; both variants bind `:data` (`DataBindingsModule` through `di/AppDataModule.kt`, D-081); goldens in `app/src/test/screenshots/` | ## Architecture check `architectureCheck` runs in every module's `check` (`build-logic/.../ArchitectureRules.kt` holds the rules as data). It fails on a project dependency outside the §6.2 table, on `:core:fakes` in any configuration that is not a test or -debug configuration, and, in `:core:model` and `:domain`, on an Android plugin, on a library outside +debug configuration, on a module's test fixtures outside a test configuration, and, in `:core:model` and `:domain`, on an Android plugin, on a library outside `org.jetbrains.kotlin*`, and on any import or fully qualified reference outside `kotlin.*`, `kotlinx.*` and `se.hagstedt.kvitt.*` in `src/main`. A new module must be added to `ArchitectureRules.allowed`. @@ -45,7 +45,7 @@ debug configuration, and, in `:core:model` and `:domain`, on an Android plugin, A stateless overload that takes the `*State` and event lambdas is what previews and screenshot tests render. * ViewModels are `@HiltViewModel` classes that receive repositories **through the interfaces of `:core:model`** only. - `:app` binds those interfaces to `:data` with Hilt (release since `e6ad858`, debug too once M8 deletes the temporary debug fakes module); tests and previews use `:core:fakes`. + `:app` binds those interfaces to `:data` with Hilt in both variants; tests and previews use `:core:fakes`. * Features never reference another feature, the `NavHost` or `:data`/`:domain`. `:app` owns the `NavHost` and wires the lambdas. * Strings live in the feature's own `src/main/res/values/strings.xml` and `values-sv/strings.xml`, keys prefixed with @@ -55,7 +55,12 @@ debug configuration, and, in `:core:model` and `:domain`, on an Android plugin, ## Screenshot tests -Roborazzi on Robolectric. `roborazzi.test.verify=true` in `gradle.properties` makes every unit-test run compare with the +Roborazzi on Robolectric, wired by `kvitt.android.screenshots` (the plugin, `outputDir` `src/test/screenshots` and the +test libraries; the feature plugin applies it). The harness is shared: `:core:design`'s test fixtures +(`src/testFixtures`, package `se.hagstedt.kvitt.design.testing`, `testImplementation(testFixtures(project(":core:design")))`, +added to every feature by its plugin) hold `Variant`, `TestClock`, `TestEnvironment`, `ScreenSnapshots` (capture modes +`Root`, `Screen` and `ScreenWhenIdle`), `MainDispatcherRule` and `withMain`. A module adds only its own frame around +the content (`AnalyticsSnapshots`, `SettleSnapshots`, `SettingsSnapshots`, `:core:design`'s `Snapshots`). `roborazzi.test.verify=true` in `gradle.properties` makes every unit-test run compare with the committed goldens. Re-record an intended change with `tools/gradle-serial.sh recordRoborazziDebug` and open every changed PNG before committing. diff --git a/android/app/src/main/kotlin/se/hagstedt/kvitt/KvittEmptyScreen.kt b/android/app/src/main/kotlin/se/hagstedt/kvitt/KvittEmptyScreen.kt index ac02d9f..94e8874 100644 --- a/android/app/src/main/kotlin/se/hagstedt/kvitt/KvittEmptyScreen.kt +++ b/android/app/src/main/kotlin/se/hagstedt/kvitt/KvittEmptyScreen.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -/** The M2b shell: the themed surface with the app name. M8 replaces it with the navigation graph. */ +/** The themed surface with the app name, shown until the session state has loaded (KvittApp). */ @Composable fun KvittEmptyScreen(modifier: Modifier = Modifier) { Surface(modifier = modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { diff --git a/android/app/src/main/kotlin/se/hagstedt/kvitt/MainActivity.kt b/android/app/src/main/kotlin/se/hagstedt/kvitt/MainActivity.kt index c10cd24..a92712b 100644 --- a/android/app/src/main/kotlin/se/hagstedt/kvitt/MainActivity.kt +++ b/android/app/src/main/kotlin/se/hagstedt/kvitt/MainActivity.kt @@ -28,7 +28,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() if (savedInstanceState == null) app.onLink(AppLinks.parse(intent)) - // `:core:design` reads "now" and the zone from :data's clock (M6 contract request). + // `:core:design` reads "now" and the zone from :data's clock. val kvittClock = KvittClock({ Instant.fromEpochMilliseconds(clock.nowMillis()) }, TimeZone.of(clock.zone().id)) setContent { KvittTheme { diff --git a/android/build-logic/src/main/kotlin/se/hagstedt/kvitt/buildlogic/ArchitecturePlugin.kt b/android/build-logic/src/main/kotlin/se/hagstedt/kvitt/buildlogic/ArchitecturePlugin.kt index d642985..b379af0 100644 --- a/android/build-logic/src/main/kotlin/se/hagstedt/kvitt/buildlogic/ArchitecturePlugin.kt +++ b/android/build-logic/src/main/kotlin/se/hagstedt/kvitt/buildlogic/ArchitecturePlugin.kt @@ -7,7 +7,7 @@ import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.plugins.BasePlugin import org.gradle.language.base.plugins.LifecycleBasePlugin -/** Registers `architectureCheck` in every module and makes `check` depend on it (SPEC §6.2, owned by M2b). */ +/** Registers `architectureCheck` in every module and makes `check` depend on it (SPEC §6.2). */ class ArchitecturePlugin : Plugin { override fun apply(target: Project) { with(target) { diff --git a/android/core/design/src/debug/kotlin/se/hagstedt/kvitt/design/catalogue/Catalogue.kt b/android/core/design/src/debug/kotlin/se/hagstedt/kvitt/design/catalogue/Catalogue.kt index 9c320ea..ca17bae 100644 --- a/android/core/design/src/debug/kotlin/se/hagstedt/kvitt/design/catalogue/Catalogue.kt +++ b/android/core/design/src/debug/kotlin/se/hagstedt/kvitt/design/catalogue/Catalogue.kt @@ -569,7 +569,7 @@ fun TopBarSheet() { KvittTopAppBar(w("Ny utgift", "New expense"), navigation = TopBarNavigation.Close, actions = { KvittButton(w("Spara", "Save"), {}, icon = KvittIcon.Check) }) - // Edge to edge (M8b): a 24 dp status-bar inset as on a phone; the fill runs behind it, the content sits below it. + // Edge to edge: a 24 dp status-bar inset as on a phone; the fill runs behind it, the content sits below it. KvittTopAppBar(w("Statistik", "Analytics"), scrolled = true, windowInsets = WindowInsets(top = StatusBarSample)) } diff --git a/android/core/design/src/main/kotlin/se/hagstedt/kvitt/design/component/Scaffold.kt b/android/core/design/src/main/kotlin/se/hagstedt/kvitt/design/component/Scaffold.kt index 0d5601a..4843d58 100644 --- a/android/core/design/src/main/kotlin/se/hagstedt/kvitt/design/component/Scaffold.kt +++ b/android/core/design/src/main/kotlin/se/hagstedt/kvitt/design/component/Scaffold.kt @@ -360,7 +360,7 @@ private const val INVITE_MAX_LINES = 3 /** * Groups the raw invite code for display (S-1): "7K3MQX9D2HPA" shows as "7K3M-QX9D-2HPA". The field's value stays the raw * normalised code, so the platform keeps the cursor where the user typed and never lands it before a hyphen that - * appeared after the fourth or eighth character (M8b). A hyphen is shown only between groups, followed by a zero-width + * appeared after the fourth or eighth character. A hyphen is shown only between groups, followed by a zero-width * space so a line may break after any group: the line breaker does not break between a hyphen and a digit * ("QX9D-2HPA"), which cut the value at 200 % font scale. A cursor at a group boundary sits after the separator. */ diff --git a/android/core/design/src/main/kotlin/se/hagstedt/kvitt/design/theme/KvittDimens.kt b/android/core/design/src/main/kotlin/se/hagstedt/kvitt/design/theme/KvittDimens.kt index fba3215..142797a 100644 --- a/android/core/design/src/main/kotlin/se/hagstedt/kvitt/design/theme/KvittDimens.kt +++ b/android/core/design/src/main/kotlin/se/hagstedt/kvitt/design/theme/KvittDimens.kt @@ -228,7 +228,7 @@ object KvittDimens { const val DISABLED_CONTAINER_ALPHA = 0.12f const val DELETED_ALPHA = 0.62f - // Screen measures of design-spec §4 that the feature modules use (moved here from the features, M6b). + // Screen measures of design-spec §4 that the feature modules use (no feature writes a size of its own). /** App mark (Sunny, `primary`, `receipt_long`): S-1 brand block 88 with icon 44; S-22 About 96 with icon 48. */ val appMark = 88.dp val appMarkIcon = 44.dp diff --git a/android/core/design/src/test/kotlin/se/hagstedt/kvitt/design/component/InviteCodeFieldTest.kt b/android/core/design/src/test/kotlin/se/hagstedt/kvitt/design/component/InviteCodeFieldTest.kt index 4bd6871..d34d761 100644 --- a/android/core/design/src/test/kotlin/se/hagstedt/kvitt/design/component/InviteCodeFieldTest.kt +++ b/android/core/design/src/test/kotlin/se/hagstedt/kvitt/design/component/InviteCodeFieldTest.kt @@ -6,7 +6,7 @@ import org.junit.Assert.assertTrue import org.junit.Test /** - * The invite code field's grouping (S-1, M8b): the value is the raw normalised code and the hyphens are display only, so + * The invite code field's grouping (S-1): the value is the raw normalised code and the hyphens are display only, so * typing one character at a time keeps the cursor after the last character, and deleting across a hyphen removes the * character before it. Each step mirrors the field: the edit lands at the original cursor, the field normalises the * text, and the cursor is shown through the offset mapping. diff --git a/android/core/design/src/test/kotlin/se/hagstedt/kvitt/design/component/ScaffoldSnackbarTest.kt b/android/core/design/src/test/kotlin/se/hagstedt/kvitt/design/component/ScaffoldSnackbarTest.kt index 0fde2b6..51af3fd 100644 --- a/android/core/design/src/test/kotlin/se/hagstedt/kvitt/design/component/ScaffoldSnackbarTest.kt +++ b/android/core/design/src/test/kotlin/se/hagstedt/kvitt/design/component/ScaffoldSnackbarTest.kt @@ -28,7 +28,7 @@ import se.hagstedt.kvitt.design.KvittTheme import java.util.Locale /** - * A snackbar shown with [showKvittSnackbar] follows a language change while it is on screen (M6b item 5), and + * A snackbar shown with [showKvittSnackbar] follows a language change while it is on screen, and * [styledStringResource] keeps the `` parts of a resource. */ @RunWith(RobolectricTestRunner::class) diff --git a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/account/AccountScenarios.kt b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/account/AccountScenarios.kt index 2933f15..ae93914 100644 --- a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/account/AccountScenarios.kt +++ b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/account/AccountScenarios.kt @@ -7,7 +7,7 @@ import se.hagstedt.kvitt.model.Account import se.hagstedt.kvitt.model.AccountId /** - * Scenarios of the account screens (S-5, S-6; M7b) on the household of [HomeScenarios]. Open S-5 with the account ids + * Scenarios of the account screens (S-5, S-6) on the household of [HomeScenarios]. Open S-5 with the account ids * named here; S-6 creates or edits against the same household. */ object AccountScenarios { diff --git a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/analytics/AnalyticsScenarios.kt b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/analytics/AnalyticsScenarios.kt index bbc8981..2019399 100644 --- a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/analytics/AnalyticsScenarios.kt +++ b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/analytics/AnalyticsScenarios.kt @@ -19,7 +19,7 @@ import se.hagstedt.kvitt.model.Share import se.hagstedt.kvitt.model.SplitMode /** - * Scenarios of S-11 (analytics) and S-12 (drill-down), M7e. All share FakeScenarios.empty's household and clock + * Scenarios of S-11 (analytics) and S-12 (drill-down). All share FakeScenarios.empty's household and clock * (today 2026-10-17); the expense data is plain and deterministic so every screenshot is reproducible. * * - [loaded]: May to October 2026, eleven default categories, four accounts (one archived), settlements none. diff --git a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/home/HomeScenarios.kt b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/home/HomeScenarios.kt index c321308..7cf821f 100644 --- a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/home/HomeScenarios.kt +++ b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/home/HomeScenarios.kt @@ -23,7 +23,7 @@ import se.hagstedt.kvitt.model.UpdateState import se.hagstedt.kvitt.model.UserId /** - * The household of the design spec's frames (S-4, S-5, S-16, S-17; M7b): Jacob (the viewer) and Anna, the accounts + * The household of the design spec's frames (S-4, S-5, S-16, S-17): Jacob (the viewer) and Anna, the accounts * Vardag, Huset, Japan 2027 and Gotland 2026 (archived). The phone's clock is 17 October 2026 14:02 in Stockholm, the * last sync three minutes earlier. Balances come from the expenses through `:domain`: Vardag +1 184,50 kr, Huset * −3 084,50 kr, Japan 2027 −1 350,00 kr, Gotland 2026 0, globally "Du är skyldig Anna 3 250,00 kr". diff --git a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/settings/ScriptedSettingsRepositories.kt b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/settings/ScriptedSettingsRepositories.kt index 1ec9ecd..19c6bde 100644 --- a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/settings/ScriptedSettingsRepositories.kt +++ b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/settings/ScriptedSettingsRepositories.kt @@ -45,7 +45,7 @@ class OnlineScript { /** * The fakes of [FakeRepositories] with the online calls of the settings screens routed through one [script]: the - * error, loading and in-progress states of S-14, S-15, S-19, S-20, S-21 and S-22 (M7f). Reads pass straight through. + * error, loading and in-progress states of S-14, S-15, S-19, S-20, S-21 and S-22. Reads pass straight through. */ class ScriptedSettingsRepositories(scenario: FakeScenario) { val fakes = FakeRepositories(scenario) diff --git a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/settings/SettingsScenarios.kt b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/settings/SettingsScenarios.kt index 8628be8..6443853 100644 --- a/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/settings/SettingsScenarios.kt +++ b/android/core/fakes/src/main/kotlin/se/hagstedt/kvitt/fakes/settings/SettingsScenarios.kt @@ -29,7 +29,7 @@ import se.hagstedt.kvitt.model.User import se.hagstedt.kvitt.model.UserId /** - * Scenarios of `:feature:settings` (M7f; S-13 to S-15, S-19 to S-22), as in the design spec's frames: Jacob (admin) and + * Scenarios of `:feature:settings` (S-13 to S-15, S-19 to S-22), as in the design spec's frames: Jacob (admin) and * Anna, the accounts Vardag, Huset and Japan 2027, the 14 defaults plus the archived custom "Pant", four recurring * templates, both phones and one pending device invite. The phone's clock is 17 October 2026 14:02 Stockholm. */ diff --git a/android/core/model/src/main/kotlin/se/hagstedt/kvitt/model/analytics/Analytics.kt b/android/core/model/src/main/kotlin/se/hagstedt/kvitt/model/analytics/Analytics.kt index f9e2506..82e0fa9 100644 --- a/android/core/model/src/main/kotlin/se/hagstedt/kvitt/model/analytics/Analytics.kt +++ b/android/core/model/src/main/kotlin/se/hagstedt/kvitt/model/analytics/Analytics.kt @@ -2,7 +2,7 @@ package se.hagstedt.kvitt.model.analytics import kotlinx.datetime.LocalDate -// Analytics input and output types (SPEC §14.5.4), package per the orchestrator's M2b decision. +// Analytics input and output types (SPEC §14.5.4), package se.hagstedt.kvitt.model.analytics (D-017). data class AnalyticsExpense(val date: LocalDate, val accountId: String, val categoryId: String, val amountOre: Long, val shares: Map) diff --git a/android/data/src/main/kotlin/se/hagstedt/kvitt/data/di/DataModule.kt b/android/data/src/main/kotlin/se/hagstedt/kvitt/data/di/DataModule.kt index 91e738a..83b0362 100644 --- a/android/data/src/main/kotlin/se/hagstedt/kvitt/data/di/DataModule.kt +++ b/android/data/src/main/kotlin/se/hagstedt/kvitt/data/di/DataModule.kt @@ -87,7 +87,7 @@ import javax.inject.Provider import javax.inject.Qualifier import javax.inject.Singleton -/** What `:app` (M8) provides: its version (§10.6) and build type (AC-2.8, AC-16.5). */ +/** What `:app` provides: its version (§10.6) and build type (AC-2.8, AC-16.5). */ data class AppBuildInfo(val versionCode: Int, val versionName: String, val debug: Boolean) @Qualifier diff --git a/android/data/src/main/kotlin/se/hagstedt/kvitt/data/sync/SyncEngine.kt b/android/data/src/main/kotlin/se/hagstedt/kvitt/data/sync/SyncEngine.kt index 57fd7d8..0816702 100644 --- a/android/data/src/main/kotlin/se/hagstedt/kvitt/data/sync/SyncEngine.kt +++ b/android/data/src/main/kotlin/se/hagstedt/kvitt/data/sync/SyncEngine.kt @@ -214,11 +214,6 @@ class SyncEngine( } } - /** - * `clock_skew` keeps the row pending (§14.4.5). The server stores rejected results by `mutation_id` and replays them - * (§14.4.4 steps 1 and 12), so the row gets a new mutation id; otherwise it could never sync after the clock is - * fixed (proposed decision in the M5 report). A row edited during the request already has a new one. - */ /** * D-027 (§14.4.5 step 3): on the first attempt after a `clock_skew` rejection, every pending row stamped later than * the phone's clock now is re-stamped to now, so a corrected clock heals it. Rows stamped by a clock that is still @@ -233,6 +228,11 @@ class SyncEngine( } } + /** + * `clock_skew` keeps the row pending (§14.4.5). The server stores rejected results by `mutation_id` and replays them + * (§14.4.4 steps 1 and 12), so the row gets a new mutation id; otherwise it could never sync after the clock is + * fixed (D-027). A row edited during the request already has a new one. + */ private suspend fun renewMutationId(pending: Pending) { val sent = pending.mutation.mutationId when (pending.entityType) { diff --git a/android/data/src/main/kotlin/se/hagstedt/kvitt/data/sync/SyncTriggers.kt b/android/data/src/main/kotlin/se/hagstedt/kvitt/data/sync/SyncTriggers.kt index 24941b2..16ce694 100644 --- a/android/data/src/main/kotlin/se/hagstedt/kvitt/data/sync/SyncTriggers.kt +++ b/android/data/src/main/kotlin/se/hagstedt/kvitt/data/sync/SyncTriggers.kt @@ -36,7 +36,7 @@ fun interface LocalWriteListener { /** * The sync triggers of AC-15.3 and §14.4.9: app start, every return to the foreground, 2 s after the last local write - * (debounced), every 15 minutes in the background, and every FCM data message (expedited). `:app` (M8) calls + * (debounced), every 15 minutes in the background, and every FCM data message (expedited). `:app` calls * [onAppStart], [onForeground] and [syncForPush]. */ class SyncTriggers( @@ -85,7 +85,7 @@ class SyncTriggers( * WorkManager wiring (§14.4.1, §14.4.9): one-off work under the unique name `kvitt-sync` with `APPEND_OR_REPLACE`, * periodic work every 15 minutes with `KEEP`, both requiring a connected network, retries with exponential backoff * from 30 seconds. Periodic and one-off work cannot share a unique name in WorkManager, so the periodic one is - * `kvitt-sync-periodic` (proposed decision in the M5 report). + * `kvitt-sync-periodic` (D-028). */ class WorkManagerSyncWork(private val context: Context) : SyncWork { private val connected = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build() @@ -127,7 +127,7 @@ class SyncWorker( } } -/** Creates [SyncWorker]s with the engine; `:app` (M8) installs it in its WorkManager `Configuration`. */ +/** Creates [SyncWorker]s with the engine; `:app` installs it in its WorkManager `Configuration`. */ class KvittWorkerFactory(private val engine: () -> SyncEngine) : WorkerFactory() { override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? = if (workerClassName == SyncWorker::class.java.name) SyncWorker(appContext, workerParameters, engine()) else null diff --git a/android/data/src/test/kotlin/se/hagstedt/kvitt/data/api/ExampleRoundTripTest.kt b/android/data/src/test/kotlin/se/hagstedt/kvitt/data/api/ExampleRoundTripTest.kt index 12bf109..1f52aed 100644 --- a/android/data/src/test/kotlin/se/hagstedt/kvitt/data/api/ExampleRoundTripTest.kt +++ b/android/data/src/test/kotlin/se/hagstedt/kvitt/data/api/ExampleRoundTripTest.kt @@ -12,7 +12,7 @@ import org.junit.runners.Parameterized import java.io.File /** - * SPEC §14.3.5, M5 gate: every file under `contracts/examples/` decodes into its DTO and re-encodes to the same JSON + * SPEC §14.3.5: every file under `contracts/examples/` decodes into its DTO and re-encodes to the same JSON * value (key order aside). One test case per file, so the case count equals the file count; [ExampleCoverageTest] * checks that no file is skipped. Sync bodies also round-trip their nested `entity` / `current` objects. */ diff --git a/android/feature/expense/src/test/kotlin/se/hagstedt/kvitt/feature/expense/ExpenseEditorViewModelTest.kt b/android/feature/expense/src/test/kotlin/se/hagstedt/kvitt/feature/expense/ExpenseEditorViewModelTest.kt index 7eb15b0..94c487c 100644 --- a/android/feature/expense/src/test/kotlin/se/hagstedt/kvitt/feature/expense/ExpenseEditorViewModelTest.kt +++ b/android/feature/expense/src/test/kotlin/se/hagstedt/kvitt/feature/expense/ExpenseEditorViewModelTest.kt @@ -262,7 +262,7 @@ class ExpenseEditorViewModelTest { fun editedAmounts_keepBothDecimals() = runTest(dispatcher) { val (vm, _) = editor(args = mapOf(ExpenseEditorViewModel.ARG_EXPENSE_ID to ExpenseScenarios.icaId.value)) assertEquals("1247.60", vm.state.value.form?.amount?.raw) - // The feature's own moneyInput is gone: :core:design's AmountInput.of restores money with both decimals (M6b). + // :core:design's AmountInput.of restores money with both decimals. assertEquals("412.00", AmountInput.of(41_200).raw) assertEquals("0.05", AmountInput.of(5).raw) } diff --git a/android/feature/settings/src/main/res/values-sv/strings.xml b/android/feature/settings/src/main/res/values-sv/strings.xml index 3171464..3418b1e 100644 --- a/android/feature/settings/src/main/res/values-sv/strings.xml +++ b/android/feature/settings/src/main/res/values-sv/strings.xml @@ -1,5 +1,5 @@ - Kräver anslutning. Profilen kan bara ändras när telefonen är online. diff --git a/android/feature/settings/src/main/res/values/strings.xml b/android/feature/settings/src/main/res/values/strings.xml index d4f7cf8..6717bca 100644 --- a/android/feature/settings/src/main/res/values/strings.xml +++ b/android/feature/settings/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - Needs connection. Your profile can only be changed online. diff --git a/android/feature/settle/src/main/kotlin/se/hagstedt/kvitt/feature/settle/SettleViewModel.kt b/android/feature/settle/src/main/kotlin/se/hagstedt/kvitt/feature/settle/SettleViewModel.kt index e020eab..21cc143 100644 --- a/android/feature/settle/src/main/kotlin/se/hagstedt/kvitt/feature/settle/SettleViewModel.kt +++ b/android/feature/settle/src/main/kotlin/se/hagstedt/kvitt/feature/settle/SettleViewModel.kt @@ -27,7 +27,7 @@ import se.hagstedt.kvitt.model.User import javax.inject.Inject /** - * Navigation arguments of S-9 and S-10, read from the [SavedStateHandle] (`:app` puts them there, M8). S-9: none for + * Navigation arguments of S-9 and S-10, read from the [SavedStateHandle] (`:app` puts them there). S-9: none for * the global settle-up, [ACCOUNT_ID] for the per-account variant, [SETTLEMENT_ID] for editing. S-10: [SETTLEMENT_ID]. */ object SettleArgs { diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 7c5b579..a5a6ec4 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -1,5 +1,5 @@ -# Kvitt version catalog. Versions are the latest stable releases on Google Maven / Maven Central as of 2026-10-17 -# (milestone M2b). Drift: python3 tools/dependency-report/latest.py +# Kvitt version catalog: the latest stable releases on Google Maven / Maven Central (no drift on 2026-10-18). +# Drift: python3 tools/dependency-report/latest.py [versions] minSdk = "36" compileSdk = "37" @@ -27,8 +27,8 @@ coil = "3.6.3" junit = "4.13.2" robolectric = "4.17" roborazzi = "1.76.0" -# M8 (2026-10-18): Firebase BoM 35.0.2 (firebase-messaging 26.0.0) and the Google services plugin 4.5.0, the latest -# stable per firebase.google.com/support/release-notes/android +# Firebase BoM 35.0.2 (firebase-messaging 26.0.0) and the Google services plugin 4.5.0, the latest stable per +# firebase.google.com/support/release-notes/android on 2026-10-18 firebaseBom = "35.0.2" googleServices = "4.5.0"