From d25809b87caae68843967bfc44c455d2ca52554f Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 17:55:52 -0400 Subject: [PATCH 01/17] build(desktop): add the Compose Desktop shell and Android res as Compose resources :apps:desktop is a kotlin.jvm + compose.desktop app with an empty Metro graph. ui:resources now also generates a Compose Multiplatform Res class from its Android res/ through a Sync task, so common code can read the same strings and drawables without a second copy in the repo. --- apps/desktop/build.gradle.kts | 28 +++++++++++++++++++ .../com/flipcash/desktop/DesktopGraph.kt | 13 +++++++++ .../main/kotlin/com/flipcash/desktop/Main.kt | 25 +++++++++++++++++ .../resources/AndroidResAsComposeResources.kt | 27 ++++++++++++++++++ build.gradle.kts | 3 ++ gradle/libs.versions.toml | 12 ++++++++ settings.gradle.kts | 1 + ui/resources/build.gradle.kts | 13 +++++++++ 8 files changed, 122 insertions(+) create mode 100644 apps/desktop/build.gradle.kts create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt create mode 100644 build-logic/convention/src/main/kotlin/com/getcode/buildlogic/resources/AndroidResAsComposeResources.kt diff --git a/apps/desktop/build.gradle.kts b/apps/desktop/build.gradle.kts new file mode 100644 index 0000000000..2fc3dfc4c3 --- /dev/null +++ b/apps/desktop/build.gradle.kts @@ -0,0 +1,28 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.jetbrains.compose) + alias(libs.plugins.metro) +} + +kotlin { + jvmToolchain(21) +} + +dependencies { + implementation(compose.desktop.currentOs) + implementation(libs.jetbrains.compose.material3) +} + +compose.desktop { + application { + mainClass = "com.flipcash.desktop.MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "Flipcash" + packageVersion = "1.0.0" + } + } +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt new file mode 100644 index 0000000000..78b62aade5 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt @@ -0,0 +1,13 @@ +package com.flipcash.desktop + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.DependencyGraph +import dev.zacsweers.metro.Provides + +@DependencyGraph(AppScope::class) +interface DesktopGraph { + val appName: String + + @Provides + fun provideAppName(): String = "Flipcash" +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt new file mode 100644 index 0000000000..8f9fbed3a2 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt @@ -0,0 +1,25 @@ +package com.flipcash.desktop + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import dev.zacsweers.metro.createGraph +import java.lang.management.ManagementFactory + +fun main() { + val graph = createGraph() + application { + Window(onCloseRequest = ::exitApplication, title = "Flipcash") { + MaterialTheme { + Text(graph.appName) + } + LaunchedEffect(Unit) { + val sinceJvmStart = System.currentTimeMillis() - + ManagementFactory.getRuntimeMXBean().startTime + println("startup: first composition ${sinceJvmStart}ms after JVM start") + } + } + } +} diff --git a/build-logic/convention/src/main/kotlin/com/getcode/buildlogic/resources/AndroidResAsComposeResources.kt b/build-logic/convention/src/main/kotlin/com/getcode/buildlogic/resources/AndroidResAsComposeResources.kt new file mode 100644 index 0000000000..aa98c13b88 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/com/getcode/buildlogic/resources/AndroidResAsComposeResources.kt @@ -0,0 +1,27 @@ +package com.getcode.buildlogic.resources + +import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Sync + +/** + * Exposes a module's Android `res/` directory to Compose Multiplatform resources. + * + * Android keeps reading `res/` through `R`, so its behaviour does not change; common code reads + * the same strings and drawables through the generated `Res` class. The files stay in one place; + * this only maps the layout. Compose resources reject Android's `nodpi` qualifier, so + * `drawable-nodpi` is served as plain `drawable`. Pass the result to + * `compose.resources.customDirectory(sourceSetName = "commonMain", directoryProvider = ...)`. + */ +fun Project.androidResAsComposeResources(resDir: String = "src/androidMain/res"): Provider { + val sync = tasks.register("syncAndroidResForCompose", Sync::class.java) { + from(layout.projectDirectory.dir(resDir)) { + include("values/**", "drawable/**", "drawable-*/**", "font/**") + } + eachFile { path = path.replaceFirst("drawable-nodpi/", "drawable/") } + includeEmptyDirs = false + into(layout.buildDirectory.dir("generated/androidResForCompose")) + } + return layout.dir(sync.map { it.destinationDir }) +} diff --git a/build.gradle.kts b/build.gradle.kts index d9c3f8bee4..1033c682a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,6 +31,9 @@ plugins { alias(libs.plugins.kotlin.ksp) apply false alias(libs.plugins.hilt) apply false alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.jetbrains.compose) apply false + alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.metro) apply false alias(libs.plugins.google.services) apply false alias(libs.plugins.firebase.perf) apply false alias(libs.plugins.bugsnag.gradle) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b5f8ec6c08..0b98382696 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ android-java = "21" kotlin = "2.4.21" ksp = "2.3.12" +metro = "1.4.5" kotlinx-coroutines = "1.11.0" kotlinx-serialization = "1.11.0" kotlinx-datetime = "0.8.0" @@ -47,6 +48,9 @@ compose-coil = "3.6.3" hilt = "2.59.2" hilt-jetpack = "1.4.0" compose-multiplatform = "1.12.1" +# JetBrains publishes material3 and the extended icons on their own lines; the icons stopped at 1.7.3. +compose-multiplatform-material3 = "1.12.0-alpha03" +compose-multiplatform-icons = "1.7.3" okhttp = "5.5.0" retrofit = "3.0.0" kin-sdk = "2.1.2" @@ -341,6 +345,11 @@ jetbrains-compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", # On Android these resolve to the androidx artifacts at the version the Compose BOM pins. jetbrains-compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "compose-multiplatform" } jetbrains-compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "compose-multiplatform" } +jetbrains-compose-material = { module = "org.jetbrains.compose.material:material", version.ref = "compose-multiplatform" } +jetbrains-compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "compose-multiplatform-material3" } +jetbrains-compose-material-icons-extended = { module = "org.jetbrains.compose.material:material-icons-extended", version.ref = "compose-multiplatform-icons" } +jetbrains-compose-components-preview = { module = "org.jetbrains.compose.components:components-ui-tooling-preview", version.ref = "compose-multiplatform" } +jetbrains-compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "compose-multiplatform" } [bundles] compose = ["compose-ui", "compose-foundation", "compose-material", "compose-material-icons-extended", "compose-animation", "compose-activities"] @@ -383,4 +392,7 @@ screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot" kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } +jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +metro = { id = "dev.zacsweers.metro", version.ref = "metro" } kmmbridge = { id = "co.touchlab.kmmbridge.github", version.ref = "kmmbridge" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 99cf20e5ba..6f3b8c4dee 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -111,6 +111,7 @@ rootProject.name = "Flipcash" include( // app containers + ":apps:desktop", ":apps:flipcash:app", ":apps:flipcash:benchmark", diff --git a/ui/resources/build.gradle.kts b/ui/resources/build.gradle.kts index 47fa09cfd2..2c59e04111 100644 --- a/ui/resources/build.gradle.kts +++ b/ui/resources/build.gradle.kts @@ -1,6 +1,9 @@ +import com.getcode.buildlogic.resources.androidResAsComposeResources + plugins { alias(libs.plugins.flipcash.kmp.app.library) alias(libs.plugins.compose.compiler) + alias(libs.plugins.jetbrains.compose) } kotlin { @@ -18,6 +21,7 @@ kotlin { api(libs.kotlinx.coroutines.core) api(libs.jetbrains.compose.runtime) api(libs.jetbrains.compose.ui) + api(libs.jetbrains.compose.components.resources) } androidMain.dependencies { api(libs.androidx.appcompat) @@ -27,3 +31,12 @@ kotlin { } } } + +compose.resources { + publicResClass = true + packageOfResClass = "${Gradle.codeNamespace}.util.resources" + customDirectory( + sourceSetName = "commonMain", + directoryProvider = androidResAsComposeResources(), + ) +} From ae1ec91bd5bafee985b60113d9d2d1d1541db196 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:06:17 -0400 Subject: [PATCH 02/17] build(theme): move ui:theme to android+jvm KMP Fonts load through an expect seam: Android keeps R.font, desktop reads the same files from the classpath. Dynamic color and the View helpers stay in androidMain. --- gradle/libs.versions.toml | 1 + ui/theme/build.gradle.kts | 45 +++++++----- .../kotlin/com/getcode/theme/DynamicColor.kt | 0 .../com/getcode/theme/Platform.android.kt | 66 ++++++++++++++++++ .../kotlin/com/getcode/ui/utils/View.kt | 0 .../res/font/avenir_next_demi.otf | Bin .../res/font/avenir_next_medium.otf | Bin .../res/font/avenir_next_regular.otf | Bin .../res/font/roboto_mono_variable.ttf | Bin .../animation/SharedTransitionScope.kt | 0 .../kotlin/com/getcode/theme/Color.kt | 4 +- .../kotlin/com/getcode/theme/Dimens.kt | 14 ++-- .../kotlin/com/getcode/theme/DropShadow.kt | 8 +-- .../kotlin/com/getcode/theme/Platform.kt | 20 ++++++ .../kotlin/com/getcode/theme/Shape.kt | 0 .../kotlin/com/getcode/theme/Theme.kt | 9 +-- .../kotlin/com/getcode/theme/Type.kt | 30 +------- .../com/getcode/theme/WindowSizeClass.kt | 16 ++--- .../getcode/view/shapes/TriangleCutShape.kt | 7 +- .../kotlin/com/getcode/theme/Platform.jvm.kt | 65 +++++++++++++++++ 20 files changed, 204 insertions(+), 81 deletions(-) rename ui/theme/src/{main => androidMain}/kotlin/com/getcode/theme/DynamicColor.kt (100%) create mode 100644 ui/theme/src/androidMain/kotlin/com/getcode/theme/Platform.android.kt rename ui/theme/src/{main => androidMain}/kotlin/com/getcode/ui/utils/View.kt (100%) rename ui/theme/src/{main => androidMain}/res/font/avenir_next_demi.otf (100%) rename ui/theme/src/{main => androidMain}/res/font/avenir_next_medium.otf (100%) rename ui/theme/src/{main => androidMain}/res/font/avenir_next_regular.otf (100%) rename ui/theme/src/{main => androidMain}/res/font/roboto_mono_variable.ttf (100%) rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/animation/SharedTransitionScope.kt (100%) rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/theme/Color.kt (96%) rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/theme/Dimens.kt (92%) rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/theme/DropShadow.kt (89%) create mode 100644 ui/theme/src/commonMain/kotlin/com/getcode/theme/Platform.kt rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/theme/Shape.kt (100%) rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/theme/Theme.kt (97%) rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/theme/Type.kt (78%) rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/theme/WindowSizeClass.kt (71%) rename ui/theme/src/{main => commonMain}/kotlin/com/getcode/view/shapes/TriangleCutShape.kt (93%) create mode 100644 ui/theme/src/jvmMain/kotlin/com/getcode/theme/Platform.jvm.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0b98382696..0c0ed05e95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -349,6 +349,7 @@ jetbrains-compose-material = { module = "org.jetbrains.compose.material:material jetbrains-compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "compose-multiplatform-material3" } jetbrains-compose-material-icons-extended = { module = "org.jetbrains.compose.material:material-icons-extended", version.ref = "compose-multiplatform-icons" } jetbrains-compose-components-preview = { module = "org.jetbrains.compose.components:components-ui-tooling-preview", version.ref = "compose-multiplatform" } +jetbrains-compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "compose-multiplatform" } jetbrains-compose-components-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "compose-multiplatform" } [bundles] diff --git a/ui/theme/build.gradle.kts b/ui/theme/build.gradle.kts index 408c538682..f144579d0f 100644 --- a/ui/theme/build.gradle.kts +++ b/ui/theme/build.gradle.kts @@ -1,22 +1,35 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) } -android { - namespace = "com.getcode.theme" - - buildFeatures { - buildConfig = true +kotlin { + android { + namespace = "com.getcode.theme" + androidResources { + enable = true + } } -} -dependencies { debugImplementation(libs.compose.ui.tools) - implementation(libs.compose.ui.tools.preview) - implementation(libs.compose.material) - implementation(libs.compose.material3) - implementation(libs.compose.accompanist) - - implementation(libs.androidx.appcompat) - implementation(libs.androidx.core) - implementation(libs.androidx.activity) + sourceSets { + commonMain.dependencies { + implementation(project(":libs:logging")) + api(libs.jetbrains.compose.runtime) + api(libs.jetbrains.compose.foundation) + api(libs.jetbrains.compose.ui) + api(libs.jetbrains.compose.material) + implementation(libs.jetbrains.compose.ui.tooling.preview) + } + androidMain.dependencies { + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(libs.compose.ui.tools.preview) + implementation(libs.compose.material3) + implementation(libs.compose.accompanist) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.core) + implementation(libs.androidx.activity) + } + // Desktop reads the same font files Android packages from res/font. + jvmMain.get().resources.srcDir("src/androidMain/res") + } } diff --git a/ui/theme/src/main/kotlin/com/getcode/theme/DynamicColor.kt b/ui/theme/src/androidMain/kotlin/com/getcode/theme/DynamicColor.kt similarity index 100% rename from ui/theme/src/main/kotlin/com/getcode/theme/DynamicColor.kt rename to ui/theme/src/androidMain/kotlin/com/getcode/theme/DynamicColor.kt diff --git a/ui/theme/src/androidMain/kotlin/com/getcode/theme/Platform.android.kt b/ui/theme/src/androidMain/kotlin/com/getcode/theme/Platform.android.kt new file mode 100644 index 0000000000..92e8eacd8c --- /dev/null +++ b/ui/theme/src/androidMain/kotlin/com/getcode/theme/Platform.android.kt @@ -0,0 +1,66 @@ +package com.getcode.theme + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import com.google.accompanist.systemuicontroller.rememberSystemUiController + +@Composable +actual fun currentScreenSizeDp(): ScreenSizeDp { + val configuration = LocalConfiguration.current + return ScreenSizeDp(configuration.screenWidthDp, configuration.screenHeightDp) +} + +@Composable +internal actual fun TransparentSystemBars() { + val sysUiController = rememberSystemUiController() + + SideEffect { + sysUiController.setStatusBarColor(color = Color(0x01000000)) + sysUiController.setNavigationBarColor(color = Color(0x01000000)) + } +} + +actual fun Paint.makeBlur(shadowBlurRadius: Float) { + this.asFrameworkPaint().apply { + maskFilter = + android.graphics.BlurMaskFilter( + shadowBlurRadius, android.graphics.BlurMaskFilter.Blur.NORMAL) + } +} + +internal actual object PlatformFonts { + actual val avenir = FontFamily( + Font(R.font.avenir_next_regular, FontWeight.Thin), + Font(R.font.avenir_next_regular, FontWeight.ExtraLight), + Font(R.font.avenir_next_regular, FontWeight.Light), + Font(R.font.avenir_next_regular, FontWeight.Normal), + Font(R.font.avenir_next_demi, FontWeight.Medium), // TODO: find a better medium OTF + Font(R.font.avenir_next_demi, FontWeight.SemiBold), + Font(R.font.avenir_next_demi, FontWeight.Bold), + Font(R.font.avenir_next_demi, FontWeight.ExtraBold), + Font(R.font.avenir_next_demi, FontWeight.Black) + ) + + actual val avenirMedium = FontFamily( + Font(R.font.avenir_next_medium, FontWeight.Normal), + Font(R.font.avenir_next_medium, FontWeight.Medium), + // Heavier overrides (e.g. caption.copy(fontWeight = Bold)) stay on Demi, as they did under Avenir. + Font(R.font.avenir_next_demi, FontWeight.SemiBold), + Font(R.font.avenir_next_demi, FontWeight.Bold), + Font(R.font.avenir_next_demi, FontWeight.ExtraBold), + Font(R.font.avenir_next_demi, FontWeight.Black), + ) + + actual val robotoMono = FontFamily( + Font(R.font.roboto_mono_variable, FontWeight.Light), + Font(R.font.roboto_mono_variable, FontWeight.Normal), + Font(R.font.roboto_mono_variable, FontWeight.Medium), + Font(R.font.roboto_mono_variable, FontWeight.SemiBold) + ) +} diff --git a/ui/theme/src/main/kotlin/com/getcode/ui/utils/View.kt b/ui/theme/src/androidMain/kotlin/com/getcode/ui/utils/View.kt similarity index 100% rename from ui/theme/src/main/kotlin/com/getcode/ui/utils/View.kt rename to ui/theme/src/androidMain/kotlin/com/getcode/ui/utils/View.kt diff --git a/ui/theme/src/main/res/font/avenir_next_demi.otf b/ui/theme/src/androidMain/res/font/avenir_next_demi.otf similarity index 100% rename from ui/theme/src/main/res/font/avenir_next_demi.otf rename to ui/theme/src/androidMain/res/font/avenir_next_demi.otf diff --git a/ui/theme/src/main/res/font/avenir_next_medium.otf b/ui/theme/src/androidMain/res/font/avenir_next_medium.otf similarity index 100% rename from ui/theme/src/main/res/font/avenir_next_medium.otf rename to ui/theme/src/androidMain/res/font/avenir_next_medium.otf diff --git a/ui/theme/src/main/res/font/avenir_next_regular.otf b/ui/theme/src/androidMain/res/font/avenir_next_regular.otf similarity index 100% rename from ui/theme/src/main/res/font/avenir_next_regular.otf rename to ui/theme/src/androidMain/res/font/avenir_next_regular.otf diff --git a/ui/theme/src/main/res/font/roboto_mono_variable.ttf b/ui/theme/src/androidMain/res/font/roboto_mono_variable.ttf similarity index 100% rename from ui/theme/src/main/res/font/roboto_mono_variable.ttf rename to ui/theme/src/androidMain/res/font/roboto_mono_variable.ttf diff --git a/ui/theme/src/main/kotlin/com/getcode/animation/SharedTransitionScope.kt b/ui/theme/src/commonMain/kotlin/com/getcode/animation/SharedTransitionScope.kt similarity index 100% rename from ui/theme/src/main/kotlin/com/getcode/animation/SharedTransitionScope.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/animation/SharedTransitionScope.kt diff --git a/ui/theme/src/main/kotlin/com/getcode/theme/Color.kt b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Color.kt similarity index 96% rename from ui/theme/src/main/kotlin/com/getcode/theme/Color.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/theme/Color.kt index 5276f7edfe..53de9b8190 100644 --- a/ui/theme/src/main/kotlin/com/getcode/theme/Color.kt +++ b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Color.kt @@ -2,7 +2,7 @@ package com.getcode.theme import androidx.compose.foundation.text.selection.TextSelectionColors import androidx.compose.ui.graphics.Color -import com.getcode.ui.utils.toAGColor +import androidx.compose.ui.graphics.toArgb val Brand = Color(0xFF0F0C1F) val BrandLight = Color(0xFF7379A0) @@ -85,7 +85,7 @@ data class GradientSpec( val borderColor: Color? = null, val borderWidth: Float = 0f, ) { - val colorsArray = colors.map { it.toAGColor() }.toIntArray() + val colorsArray = colors.map { it.toArgb() }.toIntArray() val stopsArray = stops.toFloatArray() override fun equals(other: Any?): Boolean { diff --git a/ui/theme/src/main/kotlin/com/getcode/theme/Dimens.kt b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Dimens.kt similarity index 92% rename from ui/theme/src/main/kotlin/com/getcode/theme/Dimens.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/theme/Dimens.kt index 303fc1b09b..ba3fce980f 100644 --- a/ui/theme/src/main/kotlin/com/getcode/theme/Dimens.kt +++ b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Dimens.kt @@ -1,13 +1,11 @@ package com.getcode.theme -import android.content.res.Configuration import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.getcode.theme.BuildConfig +import com.getcode.utils.BuildFlags val topBarHeight = 56.dp @@ -17,15 +15,15 @@ internal val LocalDimens = staticCompositionLocalOf { @Composable fun calculateDimensions( - logDimensions: Boolean = BuildConfig.DEBUG, - configuration: Configuration = LocalConfiguration.current + logDimensions: Boolean = BuildFlags.isDebug, + screenSizeDp: ScreenSizeDp = currentScreenSizeDp() ): Dimensions { - val screenWidthDp = configuration.screenWidthDp - val screenHeightDp = configuration.screenHeightDp + val screenWidthDp = screenSizeDp.widthDp + val screenHeightDp = screenSizeDp.heightDp val (widthSizeClass, heightSizeClass) = rememberWindowSizeClass( - logDimensions = logDimensions, configuration = configuration + logDimensions = logDimensions, screenSizeDp = screenSizeDp ) return Dimensions( diff --git a/ui/theme/src/main/kotlin/com/getcode/theme/DropShadow.kt b/ui/theme/src/commonMain/kotlin/com/getcode/theme/DropShadow.kt similarity index 89% rename from ui/theme/src/main/kotlin/com/getcode/theme/DropShadow.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/theme/DropShadow.kt index 5a32981cbc..86b162ab5c 100644 --- a/ui/theme/src/main/kotlin/com/getcode/theme/DropShadow.kt +++ b/ui/theme/src/commonMain/kotlin/com/getcode/theme/DropShadow.kt @@ -17,13 +17,7 @@ data class DropShadow( val spread: Dp = 0.dp ) -fun Paint.makeBlur(shadowBlurRadius: Float) { - this.asFrameworkPaint().apply { - maskFilter = - android.graphics.BlurMaskFilter( - shadowBlurRadius, android.graphics.BlurMaskFilter.Blur.NORMAL) - } -} +expect fun Paint.makeBlur(shadowBlurRadius: Float) fun Modifier.dropShadow(shadow: DropShadow): Modifier { return this.then( diff --git a/ui/theme/src/commonMain/kotlin/com/getcode/theme/Platform.kt b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Platform.kt new file mode 100644 index 0000000000..d402f00b24 --- /dev/null +++ b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Platform.kt @@ -0,0 +1,20 @@ +package com.getcode.theme + +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.font.FontFamily + +/** The current window's size in dp, as Android's `Configuration.screenWidthDp`/`screenHeightDp` report it. */ +data class ScreenSizeDp(val widthDp: Int, val heightDp: Int) + +@Composable +expect fun currentScreenSizeDp(): ScreenSizeDp + +/** Makes the status and navigation bars transparent where the platform has them. */ +@Composable +internal expect fun TransparentSystemBars() + +internal expect object PlatformFonts { + val avenir: FontFamily + val avenirMedium: FontFamily + val robotoMono: FontFamily +} diff --git a/ui/theme/src/main/kotlin/com/getcode/theme/Shape.kt b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Shape.kt similarity index 100% rename from ui/theme/src/main/kotlin/com/getcode/theme/Shape.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/theme/Shape.kt diff --git a/ui/theme/src/main/kotlin/com/getcode/theme/Theme.kt b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Theme.kt similarity index 97% rename from ui/theme/src/main/kotlin/com/getcode/theme/Theme.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/theme/Theme.kt index 64b1cdbede..e3930bd270 100644 --- a/ui/theme/src/main/kotlin/com/getcode/theme/Theme.kt +++ b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Theme.kt @@ -8,7 +8,6 @@ import androidx.compose.material.Shapes import androidx.compose.material.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,7 +15,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color -import com.google.accompanist.systemuicontroller.rememberSystemUiController internal val CodeDefaultColorScheme = ColorScheme( brand = Brand, @@ -80,12 +78,7 @@ fun DesignSystem( typography: CodeTypography = codeTypography, content: @Composable () -> Unit ) { - val sysUiController = rememberSystemUiController() - - SideEffect { - sysUiController.setStatusBarColor(color = Color(0x01000000)) - sysUiController.setNavigationBarColor(color = Color(0x01000000)) - } + TransparentSystemBars() val dimensions = calculateDimensions() diff --git a/ui/theme/src/main/kotlin/com/getcode/theme/Type.kt b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Type.kt similarity index 78% rename from ui/theme/src/main/kotlin/com/getcode/theme/Type.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/theme/Type.kt index ac44d6fff0..f6381fce91 100644 --- a/ui/theme/src/main/kotlin/com/getcode/theme/Type.kt +++ b/ui/theme/src/commonMain/kotlin/com/getcode/theme/Type.kt @@ -5,43 +5,19 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.sp -private val Avenir = FontFamily( - Font(R.font.avenir_next_regular, FontWeight.Thin), - Font(R.font.avenir_next_regular, FontWeight.ExtraLight), - Font(R.font.avenir_next_regular, FontWeight.Light), - Font(R.font.avenir_next_regular, FontWeight.Normal), - Font(R.font.avenir_next_demi, FontWeight.Medium), // TODO: find a better medium OTF - Font(R.font.avenir_next_demi, FontWeight.SemiBold), - Font(R.font.avenir_next_demi, FontWeight.Bold), - Font(R.font.avenir_next_demi, FontWeight.ExtraBold), - Font(R.font.avenir_next_demi, FontWeight.Black) -) +private val Avenir: FontFamily get() = PlatformFonts.avenir // The true Medium face. `Avenir` deliberately renders FontWeight.Medium as Demi (many call sites // depend on that), so styles that need iOS's real Medium use this family instead. -private val AvenirMedium = FontFamily( - Font(R.font.avenir_next_medium, FontWeight.Normal), - Font(R.font.avenir_next_medium, FontWeight.Medium), - // Heavier overrides (e.g. caption.copy(fontWeight = Bold)) stay on Demi, as they did under Avenir. - Font(R.font.avenir_next_demi, FontWeight.SemiBold), - Font(R.font.avenir_next_demi, FontWeight.Bold), - Font(R.font.avenir_next_demi, FontWeight.ExtraBold), - Font(R.font.avenir_next_demi, FontWeight.Black), -) +private val AvenirMedium: FontFamily get() = PlatformFonts.avenirMedium -private val RobotoMono = FontFamily( - Font(R.font.roboto_mono_variable, FontWeight.Light), - Font(R.font.roboto_mono_variable, FontWeight.Normal), - Font(R.font.roboto_mono_variable, FontWeight.Medium), - Font(R.font.roboto_mono_variable, FontWeight.SemiBold) -) +private val RobotoMono: FontFamily get() = PlatformFonts.robotoMono data class CodeTypography( val displayExtraLarge: TextStyle, diff --git a/ui/theme/src/main/kotlin/com/getcode/theme/WindowSizeClass.kt b/ui/theme/src/commonMain/kotlin/com/getcode/theme/WindowSizeClass.kt similarity index 71% rename from ui/theme/src/main/kotlin/com/getcode/theme/WindowSizeClass.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/theme/WindowSizeClass.kt index 1068a1ca95..d839f0ba33 100644 --- a/ui/theme/src/main/kotlin/com/getcode/theme/WindowSizeClass.kt +++ b/ui/theme/src/commonMain/kotlin/com/getcode/theme/WindowSizeClass.kt @@ -1,11 +1,9 @@ package com.getcode.theme -import android.content.res.Configuration import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalConfiguration -import com.getcode.theme.BuildConfig -import timber.log.Timber +import com.getcode.utils.BuildFlags +import com.getcode.utils.Log data class DeviceWindowSizeClass internal constructor( val widthSizeClass: WindowSizeClass, @@ -14,11 +12,11 @@ data class DeviceWindowSizeClass internal constructor( @Composable fun rememberWindowSizeClass( - logDimensions: Boolean = BuildConfig.DEBUG, - configuration: Configuration = LocalConfiguration.current + logDimensions: Boolean = BuildFlags.isDebug, + screenSizeDp: ScreenSizeDp = currentScreenSizeDp() ): DeviceWindowSizeClass { - val screenWidthDp = configuration.screenWidthDp - val screenHeightDp = configuration.screenHeightDp + val screenWidthDp = screenSizeDp.widthDp + val screenHeightDp = screenSizeDp.heightDp return remember(screenWidthDp, screenHeightDp) { val screenWidthSizeClass = when { @@ -37,7 +35,7 @@ fun rememberWindowSizeClass( DeviceWindowSizeClass(screenWidthSizeClass, screenHeightSizeClass) }.also { if (logDimensions) { - Timber.d("dimens:: width = { dp: $screenWidthDp, class: ${it.widthSizeClass.name} }, height = { dp:$screenHeightDp, class: ${it.heightSizeClass.name} }") + Log.d("dimens:: width = { dp: $screenWidthDp, class: ${it.widthSizeClass.name} }, height = { dp:$screenHeightDp, class: ${it.heightSizeClass.name} }") } } } diff --git a/ui/theme/src/main/kotlin/com/getcode/view/shapes/TriangleCutShape.kt b/ui/theme/src/commonMain/kotlin/com/getcode/view/shapes/TriangleCutShape.kt similarity index 93% rename from ui/theme/src/main/kotlin/com/getcode/view/shapes/TriangleCutShape.kt rename to ui/theme/src/commonMain/kotlin/com/getcode/view/shapes/TriangleCutShape.kt index e29bf494f0..14ed6793f6 100644 --- a/ui/theme/src/main/kotlin/com/getcode/view/shapes/TriangleCutShape.kt +++ b/ui/theme/src/commonMain/kotlin/com/getcode/view/shapes/TriangleCutShape.kt @@ -1,6 +1,5 @@ package com.getcode.view.shapes -import android.graphics.PointF import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -29,9 +28,9 @@ class TriangleCutShape(private val cutStep: Dp = 10.dp) : androidx.compose.ui.gr private fun makeCutPath(size: Size, stepX: Float, height: Float, topClosed: Boolean): Path { val localHeight = if (topClosed) height else -height - val startPt = PointF(0f, if (topClosed) 0f else height) - val seq = listOf(PointF(0f, startPt.y)) + generateSequence(startPt) { - PointF(it.x.plus(stepX), if (it.y == startPt.y) startPt.y + localHeight else startPt.y) + val startPt = Offset(0f, if (topClosed) 0f else height) + val seq = listOf(Offset(0f, startPt.y)) + generateSequence(startPt) { + Offset(it.x.plus(stepX), if (it.y == startPt.y) startPt.y + localHeight else startPt.y) }.takeWhile { it.x <= (size.width + stepX) } return Path().apply { diff --git a/ui/theme/src/jvmMain/kotlin/com/getcode/theme/Platform.jvm.kt b/ui/theme/src/jvmMain/kotlin/com/getcode/theme/Platform.jvm.kt new file mode 100644 index 0000000000..3ea836657e --- /dev/null +++ b/ui/theme/src/jvmMain/kotlin/com/getcode/theme/Platform.jvm.kt @@ -0,0 +1,65 @@ +package com.getcode.theme + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.skiaPaint +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalWindowInfo +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.platform.Font +import org.jetbrains.skia.FilterBlurMode +import org.jetbrains.skia.MaskFilter + +@Composable +actual fun currentScreenSizeDp(): ScreenSizeDp { + val size = LocalWindowInfo.current.containerSize + val density = LocalDensity.current.density + return ScreenSizeDp((size.width / density).toInt(), (size.height / density).toInt()) +} + +@Composable +internal actual fun TransparentSystemBars() = Unit + +actual fun Paint.makeBlur(shadowBlurRadius: Float) { + // Skia's own radius-to-sigma conversion, which Android's BlurMaskFilter applies internally. + val sigma = if (shadowBlurRadius > 0f) 0.57735f * shadowBlurRadius + 0.5f else 0f + skiaPaint.maskFilter = MaskFilter.makeBlur(FilterBlurMode.NORMAL, sigma) +} + +// The same font files Android packages from res/font, put on the classpath by this module's build. +private fun font(name: String, weight: FontWeight) = Font( + identity = "$name-${weight.weight}", + data = PlatformFonts::class.java.getResourceAsStream("/font/$name")!!.use { it.readBytes() }, + weight = weight, +) + +internal actual object PlatformFonts { + actual val avenir = FontFamily( + font("avenir_next_regular.otf", FontWeight.Thin), + font("avenir_next_regular.otf", FontWeight.ExtraLight), + font("avenir_next_regular.otf", FontWeight.Light), + font("avenir_next_regular.otf", FontWeight.Normal), + font("avenir_next_demi.otf", FontWeight.Medium), + font("avenir_next_demi.otf", FontWeight.SemiBold), + font("avenir_next_demi.otf", FontWeight.Bold), + font("avenir_next_demi.otf", FontWeight.ExtraBold), + font("avenir_next_demi.otf", FontWeight.Black), + ) + + actual val avenirMedium = FontFamily( + font("avenir_next_medium.otf", FontWeight.Normal), + font("avenir_next_medium.otf", FontWeight.Medium), + font("avenir_next_demi.otf", FontWeight.SemiBold), + font("avenir_next_demi.otf", FontWeight.Bold), + font("avenir_next_demi.otf", FontWeight.ExtraBold), + font("avenir_next_demi.otf", FontWeight.Black), + ) + + actual val robotoMono = FontFamily( + font("roboto_mono_variable.ttf", FontWeight.Light), + font("roboto_mono_variable.ttf", FontWeight.Normal), + font("roboto_mono_variable.ttf", FontWeight.Medium), + font("roboto_mono_variable.ttf", FontWeight.SemiBold), + ) +} From 82d26351735eb3ae1ab98ebee777e0c971dcd639 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:17:08 -0400 Subject: [PATCH 03/17] build(ui): move ui:core and ui:components to android+jvm KMP Only the files the balance screen reaches move to commonMain; the rest stay in androidMain unchanged. Drawable lookups by Android resource id go through painterForResId, with a desktop ResourceHelper over Compose resources. Blur support and hex colour parsing get small platform seams. --- gradle/libs.versions.toml | 1 + ui/components/build.gradle.kts | 93 +++++++++++-------- .../com/getcode/ui/components/BadgeTest.kt | 0 .../getcode/ui/components/FilterChipTest.kt | 0 .../components/chat/ComposerStackingTest.kt | 0 .../ui/components/chat/TypingIndicatorTest.kt | 0 .../components/text/AnimatedNumberTextTest.kt | 0 .../components/text/NumberInputHelperTest.kt | 0 .../components/text/NumberRollMotionTest.kt | 0 .../toast/FloatingToastHostStateTest.kt | 0 .../ui/core/ExpandedTouchTargetTest.kt | 0 .../ui/utils/KeyboardControllerTest.kt | 0 .../kotlin/com/getcode/ui/Locals.kt | 0 .../getcode/ui/components/BetaIndicator.kt | 0 .../getcode/ui/components/BlurredContent.kt | 0 .../getcode/ui/components/BottomScrollEdge.kt | 0 .../com/getcode/ui/components/BulletRow.kt | 0 .../ui/components/CapsuleProgressBar.kt | 0 .../kotlin/com/getcode/ui/components/Chip.kt | 0 .../com/getcode/ui/components/ChoiceRow.kt | 0 .../ui/components/CircularIconButton.kt | 0 .../ConnectionStatusPreview.android.kt | 13 +++ .../com/getcode/ui/components/FilterChip.kt | 0 .../com/getcode/ui/components/Flippable.kt | 0 .../components/FullWidthScrollableTabRow.kt | 0 .../com/getcode/ui/components/ListItem.kt | 0 .../kotlin/com/getcode/ui/components/Modal.kt | 0 .../getcode/ui/components/NotificationDot.kt | 0 .../getcode/ui/components/OnLifecycleEvent.kt | 0 .../ui/components/OnWindowFocusedRequester.kt | 0 .../kotlin/com/getcode/ui/components/Pill.kt | 0 .../getcode/ui/components/PriceWithFlag.kt | 0 .../com/getcode/ui/components/PullToReveal.kt | 0 .../com/getcode/ui/components/SearchInput.kt | 0 .../getcode/ui/components/SegmentedControl.kt | 0 .../ui/components/SelectionContainer.kt | 0 .../ui/components/SettingsSwitchRow.kt | 0 .../getcode/ui/components/SlideToConfirm.kt | 0 .../getcode/ui/components/SwipeActionRow.kt | 0 .../com/getcode/ui/components/TextInput.kt | 0 .../com/getcode/ui/components/TitleBar.kt | 0 .../getcode/ui/components/TopScrollEdge.kt | 0 .../getcode/ui/components/VerticalDivider.kt | 0 .../getcode/ui/components/bars/BarManager.kt | 0 .../getcode/ui/components/bars/BarMessages.kt | 0 .../ui/components/bars/BottomBarContainer.kt | 0 .../ui/components/bars/TopBarContainer.kt | 0 .../ui/components/charts/ChartPoint.kt | 0 .../getcode/ui/components/charts/Trends.kt | 0 .../ui/components/chat/AnonymousAvatar.kt | 0 .../getcode/ui/components/chat/ChatInput.kt | 0 .../getcode/ui/components/chat/DateBubble.kt | 0 .../ui/components/chat/HostableAvatar.kt | 0 .../ui/components/chat/MessageNodeDefaults.kt | 0 .../ui/components/chat/TypingIndicator.kt | 0 .../ui/components/chat/UnreadSeparator.kt | 0 .../getcode/ui/components/chat/UserAvatar.kt | 0 .../getcode/ui/components/picker/Picker.kt | 0 .../restrictions/RestrictionBlockingView.kt | 0 .../ui/components/text/AmountAreaPreviews.kt | 38 ++++++++ .../ui/components/text/BlurSupport.android.kt | 6 ++ .../ui/components/text/ExpandableText.kt | 0 .../ui/components/text/SectionHeader.kt | 0 .../text/animated/DigitContainer.kt | 0 .../ui/components/text/markup/Markup.kt | 0 .../text/markup/MarkupTextHelper.kt | 0 .../ui/components/toast/FloatingToast.kt | 0 .../ui/components/toast/FloatingToastHost.kt | 0 .../components/user/social/SocialUserTitle.kt | 0 .../ui/theme/CodeBillSwipeToDismiss.kt | 0 .../kotlin/com/getcode/ui/theme/CodeButton.kt | 0 .../com/getcode/ui/theme/CodeCheckbox.kt | 0 .../kotlin/com/getcode/ui/theme/CodeKeyPad.kt | 0 .../com/getcode/ui/theme/CodeRadioButton.kt | 0 .../com/getcode/ui/theme/CodeScaffold.kt | 0 .../com/getcode/ui/theme/CodeSeedView.kt | 0 .../getcode/ui/theme/CodeSegmentedControl.kt | 0 .../kotlin/com/getcode/ui/theme/CodeSwitch.kt | 0 .../com/getcode/ui/utils/AnimationUtils.kt | 0 .../getcode/ui/utils/ComplementaryGradient.kt | 0 .../com/getcode/ui/utils/EightBitGenerator.kt | 0 .../kotlin/com/getcode/ui/utils/Geometry.kt | 0 .../com/getcode/ui/utils/KeepScreenOn.kt | 0 .../kotlin/com/getcode/ui/utils/Keyboard.kt | 0 .../kotlin/com/getcode/ui/utils/LazyList.kt | 0 .../kotlin/com/getcode/ui/utils/Maps.kt | 0 .../com/getcode/ui/utils/MeasureScope.kt | 0 .../com/getcode/ui/utils/PaddingValues.kt | 0 .../getcode/ui/utils/ParseColor.android.kt | 5 + .../com/getcode/ui/utils/PreviewParameters.kt | 0 .../ui/utils/RecompositionHighlighter.kt | 0 .../kotlin/com/getcode/ui/utils/TextStyle.kt | 0 .../kotlin/com/getcode/ui/utils/Window.kt | 0 .../res/drawable/ic_arrow_up.xml | 0 .../drawable/ic_twitter_verified_badge.xml | 0 .../ic_twitter_verified_badge_gold.xml | 0 .../ic_twitter_verified_badge_gray.xml | 0 .../res/values/strings.xml | 0 .../kotlin/com/getcode/ui/components/Badge.kt | 0 .../components/ConnectionStatusComponent.kt | 10 -- .../ui/components/glass/FloatingGlass.kt | 0 .../getcode/ui/components/text/AmountArea.kt | 40 +------- .../getcode/ui/components/text/AmountText.kt | 10 +- .../ui/components/text/AmountTextAnimated.kt | 10 +- .../ui/components/text/AnimatedNumberText.kt | 3 +- .../getcode/ui/components/text/BlurSupport.kt | 4 + .../ui/components/text/NumberInputHelper.kt | 0 .../ui/components/text/NumberRollMotion.kt | 0 .../text/animated/AnimatedPlaceholderDigit.kt | 0 .../ui/components/text/animated/Digit.kt | 0 .../text/animated/DigitAnimations.kt | 0 .../text/animated/NormalizedPrefixText.kt | 0 .../ui/theme/CodeCircularProgressIndicator.kt | 0 .../com/getcode/ui/utils/AutoSizeText.kt | 0 .../getcode/ui/utils/AutoSizeTextMeasurer.kt | 0 .../getcode/ui/utils/ColorTransformations.kt | 3 +- .../kotlin/com/getcode/ui/utils/ParseColor.kt | 4 + .../kotlin/com/getcode/ui/utils/TextUnit.kt | 0 .../ui/components/text/BlurSupport.jvm.kt | 3 + .../com/getcode/ui/utils/ParseColor.jvm.kt | 11 +++ ui/core/build.gradle.kts | 37 +++++--- .../com/getcode/ui/core/AnimationScale.kt | 0 .../com/getcode/ui/core/ContextMenuAction.kt | 0 .../getcode/ui/core/ExpandedTouchTarget.kt | 0 .../com/getcode/ui/core/Modifier.android.kt | 15 +++ .../ui/core/StarMaskVisualTransformation.kt | 0 .../ui/core/visibility/VisibilityInfo.kt | 0 .../core/visibility/VisibilityTrackerNode.kt | 0 .../kotlin/com/getcode/ui/core/IntSize.kt | 0 .../kotlin/com/getcode/ui/core/Modifier.kt | 11 --- .../com/getcode/ui/core/RestrictionType.kt | 0 .../util/resources/PainterForResId.android.kt | 8 ++ .../getcode/util/resources/PainterForResId.kt | 14 +++ .../util/resources/ComposeResourceHelper.kt | 33 +++++++ .../util/resources/ComposeResourceIds.kt | 34 +++++++ .../util/resources/PainterForResId.jvm.kt | 8 ++ 136 files changed, 294 insertions(+), 120 deletions(-) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/components/BadgeTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/components/FilterChipTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/components/chat/ComposerStackingTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/components/chat/TypingIndicatorTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/components/text/AnimatedNumberTextTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/components/text/NumberInputHelperTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/components/text/NumberRollMotionTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/components/toast/FloatingToastHostStateTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/core/ExpandedTouchTargetTest.kt (100%) rename ui/components/src/{test => androidHostTest}/kotlin/com/getcode/ui/utils/KeyboardControllerTest.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/Locals.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/BetaIndicator.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/BlurredContent.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/BottomScrollEdge.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/BulletRow.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/CapsuleProgressBar.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/Chip.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/ChoiceRow.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/CircularIconButton.kt (100%) create mode 100644 ui/components/src/androidMain/kotlin/com/getcode/ui/components/ConnectionStatusPreview.android.kt rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/FilterChip.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/Flippable.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/FullWidthScrollableTabRow.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/ListItem.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/Modal.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/NotificationDot.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/OnLifecycleEvent.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/OnWindowFocusedRequester.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/Pill.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/PriceWithFlag.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/PullToReveal.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/SearchInput.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/SegmentedControl.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/SelectionContainer.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/SettingsSwitchRow.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/SlideToConfirm.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/SwipeActionRow.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/TextInput.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/TitleBar.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/TopScrollEdge.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/VerticalDivider.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/bars/BarManager.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/bars/BarMessages.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/bars/BottomBarContainer.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/bars/TopBarContainer.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/charts/ChartPoint.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/charts/Trends.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/chat/AnonymousAvatar.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/chat/ChatInput.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/chat/DateBubble.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/chat/HostableAvatar.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/chat/MessageNodeDefaults.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/chat/TypingIndicator.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/chat/UnreadSeparator.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/chat/UserAvatar.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/picker/Picker.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/restrictions/RestrictionBlockingView.kt (100%) create mode 100644 ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/AmountAreaPreviews.kt create mode 100644 ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/BlurSupport.android.kt rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/text/ExpandableText.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/text/SectionHeader.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/text/animated/DigitContainer.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/text/markup/Markup.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/text/markup/MarkupTextHelper.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/toast/FloatingToast.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/toast/FloatingToastHost.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/components/user/social/SocialUserTitle.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeBillSwipeToDismiss.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeButton.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeCheckbox.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeKeyPad.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeRadioButton.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeScaffold.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeSeedView.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeSegmentedControl.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/theme/CodeSwitch.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/AnimationUtils.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/ComplementaryGradient.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/EightBitGenerator.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/Geometry.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/KeepScreenOn.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/Keyboard.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/LazyList.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/Maps.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/MeasureScope.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/PaddingValues.kt (100%) create mode 100644 ui/components/src/androidMain/kotlin/com/getcode/ui/utils/ParseColor.android.kt rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/PreviewParameters.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/RecompositionHighlighter.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/TextStyle.kt (100%) rename ui/components/src/{main => androidMain}/kotlin/com/getcode/ui/utils/Window.kt (100%) rename ui/components/src/{main => androidMain}/res/drawable/ic_arrow_up.xml (100%) rename ui/components/src/{main => androidMain}/res/drawable/ic_twitter_verified_badge.xml (100%) rename ui/components/src/{main => androidMain}/res/drawable/ic_twitter_verified_badge_gold.xml (100%) rename ui/components/src/{main => androidMain}/res/drawable/ic_twitter_verified_badge_gray.xml (100%) rename ui/components/src/{main => androidMain}/res/values/strings.xml (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/Badge.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/ConnectionStatusComponent.kt (82%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/glass/FloatingGlass.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/AmountArea.kt (86%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/AmountText.kt (93%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/AmountTextAnimated.kt (97%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/AnimatedNumberText.kt (99%) create mode 100644 ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/BlurSupport.kt rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/NumberInputHelper.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/NumberRollMotion.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/animated/AnimatedPlaceholderDigit.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/animated/Digit.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/animated/DigitAnimations.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/components/text/animated/NormalizedPrefixText.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/theme/CodeCircularProgressIndicator.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/utils/AutoSizeText.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/utils/AutoSizeTextMeasurer.kt (100%) rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/utils/ColorTransformations.kt (98%) create mode 100644 ui/components/src/commonMain/kotlin/com/getcode/ui/utils/ParseColor.kt rename ui/components/src/{main => commonMain}/kotlin/com/getcode/ui/utils/TextUnit.kt (100%) create mode 100644 ui/components/src/jvmMain/kotlin/com/getcode/ui/components/text/BlurSupport.jvm.kt create mode 100644 ui/components/src/jvmMain/kotlin/com/getcode/ui/utils/ParseColor.jvm.kt rename ui/core/src/{main => androidMain}/kotlin/com/getcode/ui/core/AnimationScale.kt (100%) rename ui/core/src/{main => androidMain}/kotlin/com/getcode/ui/core/ContextMenuAction.kt (100%) rename ui/core/src/{main => androidMain}/kotlin/com/getcode/ui/core/ExpandedTouchTarget.kt (100%) create mode 100644 ui/core/src/androidMain/kotlin/com/getcode/ui/core/Modifier.android.kt rename ui/core/src/{main => androidMain}/kotlin/com/getcode/ui/core/StarMaskVisualTransformation.kt (100%) rename ui/core/src/{main => androidMain}/kotlin/com/getcode/ui/core/visibility/VisibilityInfo.kt (100%) rename ui/core/src/{main => androidMain}/kotlin/com/getcode/ui/core/visibility/VisibilityTrackerNode.kt (100%) rename ui/core/src/{main => commonMain}/kotlin/com/getcode/ui/core/IntSize.kt (100%) rename ui/core/src/{main => commonMain}/kotlin/com/getcode/ui/core/Modifier.kt (98%) rename ui/core/src/{main => commonMain}/kotlin/com/getcode/ui/core/RestrictionType.kt (100%) create mode 100644 ui/resources/src/androidMain/kotlin/com/getcode/util/resources/PainterForResId.android.kt create mode 100644 ui/resources/src/commonMain/kotlin/com/getcode/util/resources/PainterForResId.kt create mode 100644 ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceHelper.kt create mode 100644 ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt create mode 100644 ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/PainterForResId.jvm.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0c0ed05e95..b8b9612939 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -344,6 +344,7 @@ androidx-foundation-layout = { group = "androidx.compose.foundation", name = "fo jetbrains-compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "compose-multiplatform" } # On Android these resolve to the androidx artifacts at the version the Compose BOM pins. jetbrains-compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "compose-multiplatform" } +jetbrains-compose-animation = { module = "org.jetbrains.compose.animation:animation", version.ref = "compose-multiplatform" } jetbrains-compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "compose-multiplatform" } jetbrains-compose-material = { module = "org.jetbrains.compose.material:material", version.ref = "compose-multiplatform" } jetbrains-compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "compose-multiplatform-material3" } diff --git a/ui/components/build.gradle.kts b/ui/components/build.gradle.kts index b66710c0a1..d578f47feb 100644 --- a/ui/components/build.gradle.kts +++ b/ui/components/build.gradle.kts @@ -1,48 +1,61 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) id("org.jetbrains.kotlin.plugin.parcelize") } -android { - namespace = "com.getcode.ui.components" +kotlin { + android { + namespace = "com.getcode.ui.components" + androidResources { enable = true } + } - buildFeatures { - buildConfig = true + sourceSets { + commonMain.dependencies { + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.animation) + implementation(libs.jetbrains.compose.material) + api(libs.jetbrains.compose.material3) + implementation(libs.jetbrains.compose.components.resources) + api(libs.jetbrains.compose.ui.tooling.preview) + // api: TopScrollEdge and the app bar take a HazeState, so callers need the type. + api(libs.bundles.haze) + api(project(":ui:core")) + implementation(project(":ui:theme")) + implementation(project(":ui:resources")) + implementation(project(":libs:network:connectivity:public")) + } + androidMain.dependencies { + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(project(":libs:datetime")) + implementation(project(":libs:encryption:ed25519")) + implementation(project(":libs:encryption:utils")) + implementation(project(":libs:currency")) + implementation(project(":libs:messaging")) + implementation(project(":libs:models")) + implementation(project(":libs:vibrator:public")) + implementation(project(":ui:navigation")) + implementation(libs.coil3) + implementation(libs.coil3.network) + implementation(libs.kotlinx.datetime) + implementation(libs.compose.animation) + implementation(libs.compose.ui.text) + implementation(libs.compose.activities) + implementation(libs.compose.material) + api(libs.compose.material3) + api(libs.compose.ui.tools.preview) + implementation(libs.compose.material.icons.extended) + implementation(libs.compose.accompanist) + implementation(libs.compose.paging) + api(libs.vico.compose) + } + getByName("androidHostTest").dependencies { + implementation(kotlin("test")) + implementation(libs.bundles.unit.testing) + implementation(libs.bundles.compose.ui.testing) + } } } -dependencies { - testImplementation(kotlin("test")) - testImplementation(libs.bundles.unit.testing) - testImplementation(libs.bundles.compose.ui.testing) - - implementation(project(":libs:datetime")) - implementation(project(":libs:encryption:ed25519")) - implementation(project(":libs:encryption:utils")) - implementation(project(":libs:currency")) - implementation(project(":libs:messaging")) - implementation(project(":libs:models")) - implementation(project(":libs:network:connectivity:public")) - implementation(project(":libs:vibrator:public")) - api(project(":ui:core")) - implementation(project(":ui:theme")) - implementation(project(":ui:resources")) - - implementation(libs.coil3) - implementation(libs.coil3.network) - implementation(libs.kotlinx.datetime) - implementation(libs.compose.animation) - implementation(libs.compose.ui.text) - implementation(libs.compose.activities) - implementation(project(":ui:navigation")) - debugApi(libs.compose.ui.tools) - api(libs.compose.ui.tools.preview) - implementation(libs.compose.material) - api(libs.compose.material3) - implementation(libs.compose.material.icons.extended) - implementation(libs.compose.accompanist) - implementation(libs.compose.paging) - api(libs.vico.compose) - // api: TopScrollEdge and the app bar take a HazeState, so callers need the type. - api(libs.bundles.haze) -} diff --git a/ui/components/src/test/kotlin/com/getcode/ui/components/BadgeTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/BadgeTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/components/BadgeTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/BadgeTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/components/FilterChipTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/FilterChipTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/components/FilterChipTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/FilterChipTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/components/chat/ComposerStackingTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/chat/ComposerStackingTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/components/chat/ComposerStackingTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/chat/ComposerStackingTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/components/chat/TypingIndicatorTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/chat/TypingIndicatorTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/components/chat/TypingIndicatorTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/chat/TypingIndicatorTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/components/text/AnimatedNumberTextTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/text/AnimatedNumberTextTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/components/text/AnimatedNumberTextTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/text/AnimatedNumberTextTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/components/text/NumberInputHelperTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/text/NumberInputHelperTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/components/text/NumberInputHelperTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/text/NumberInputHelperTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/components/text/NumberRollMotionTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/text/NumberRollMotionTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/components/text/NumberRollMotionTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/text/NumberRollMotionTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/components/toast/FloatingToastHostStateTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/toast/FloatingToastHostStateTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/components/toast/FloatingToastHostStateTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/components/toast/FloatingToastHostStateTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/core/ExpandedTouchTargetTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/core/ExpandedTouchTargetTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/core/ExpandedTouchTargetTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/core/ExpandedTouchTargetTest.kt diff --git a/ui/components/src/test/kotlin/com/getcode/ui/utils/KeyboardControllerTest.kt b/ui/components/src/androidHostTest/kotlin/com/getcode/ui/utils/KeyboardControllerTest.kt similarity index 100% rename from ui/components/src/test/kotlin/com/getcode/ui/utils/KeyboardControllerTest.kt rename to ui/components/src/androidHostTest/kotlin/com/getcode/ui/utils/KeyboardControllerTest.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/Locals.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/Locals.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/Locals.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/Locals.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/BetaIndicator.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/BetaIndicator.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/BetaIndicator.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/BetaIndicator.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/BlurredContent.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/BlurredContent.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/BlurredContent.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/BlurredContent.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/BottomScrollEdge.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/BottomScrollEdge.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/BottomScrollEdge.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/BottomScrollEdge.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/BulletRow.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/BulletRow.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/BulletRow.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/BulletRow.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/CapsuleProgressBar.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/CapsuleProgressBar.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/CapsuleProgressBar.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/CapsuleProgressBar.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/Chip.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/Chip.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/Chip.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/Chip.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/ChoiceRow.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/ChoiceRow.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/ChoiceRow.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/ChoiceRow.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/CircularIconButton.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/CircularIconButton.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/CircularIconButton.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/CircularIconButton.kt diff --git a/ui/components/src/androidMain/kotlin/com/getcode/ui/components/ConnectionStatusPreview.android.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/ConnectionStatusPreview.android.kt new file mode 100644 index 0000000000..3a68e98ba8 --- /dev/null +++ b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/ConnectionStatusPreview.android.kt @@ -0,0 +1,13 @@ +package com.getcode.ui.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import com.getcode.utils.network.NetworkState +import com.getcode.utils.network.connectivity.NetworkStateProvider + +@Preview +@Composable +fun ConnectionReconnectingPreview(@PreviewParameter(NetworkStateProvider::class) state: NetworkState) { + ConnectionStatus(state) +} diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/FilterChip.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/FilterChip.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/FilterChip.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/FilterChip.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/Flippable.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/Flippable.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/Flippable.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/Flippable.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/FullWidthScrollableTabRow.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/FullWidthScrollableTabRow.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/FullWidthScrollableTabRow.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/FullWidthScrollableTabRow.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/ListItem.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/ListItem.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/ListItem.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/ListItem.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/Modal.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/Modal.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/Modal.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/Modal.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/NotificationDot.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/NotificationDot.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/NotificationDot.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/NotificationDot.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/OnLifecycleEvent.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/OnLifecycleEvent.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/OnLifecycleEvent.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/OnLifecycleEvent.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/OnWindowFocusedRequester.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/OnWindowFocusedRequester.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/OnWindowFocusedRequester.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/OnWindowFocusedRequester.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/Pill.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/Pill.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/Pill.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/Pill.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/PriceWithFlag.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/PriceWithFlag.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/PriceWithFlag.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/PriceWithFlag.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/PullToReveal.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/PullToReveal.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/PullToReveal.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/PullToReveal.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/SearchInput.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/SearchInput.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/SearchInput.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/SearchInput.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/SegmentedControl.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/SegmentedControl.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/SegmentedControl.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/SegmentedControl.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/SelectionContainer.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/SelectionContainer.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/SelectionContainer.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/SelectionContainer.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/SettingsSwitchRow.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/SettingsSwitchRow.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/SettingsSwitchRow.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/SettingsSwitchRow.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/SlideToConfirm.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/SlideToConfirm.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/SlideToConfirm.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/SlideToConfirm.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/SwipeActionRow.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/SwipeActionRow.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/SwipeActionRow.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/SwipeActionRow.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/TextInput.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/TextInput.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/TextInput.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/TextInput.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/TitleBar.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/TitleBar.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/TitleBar.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/TitleBar.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/TopScrollEdge.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/TopScrollEdge.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/TopScrollEdge.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/TopScrollEdge.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/VerticalDivider.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/VerticalDivider.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/VerticalDivider.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/VerticalDivider.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/bars/BarManager.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/bars/BarManager.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/bars/BarManager.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/bars/BarManager.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/bars/BarMessages.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/bars/BarMessages.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/bars/BarMessages.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/bars/BarMessages.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/bars/BottomBarContainer.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/bars/BottomBarContainer.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/bars/BottomBarContainer.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/bars/BottomBarContainer.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/bars/TopBarContainer.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/bars/TopBarContainer.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/bars/TopBarContainer.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/bars/TopBarContainer.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/charts/ChartPoint.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/charts/ChartPoint.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/charts/ChartPoint.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/charts/ChartPoint.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/charts/Trends.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/charts/Trends.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/charts/Trends.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/charts/Trends.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/chat/AnonymousAvatar.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/AnonymousAvatar.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/chat/AnonymousAvatar.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/AnonymousAvatar.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/chat/ChatInput.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/ChatInput.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/chat/ChatInput.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/ChatInput.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/chat/DateBubble.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/DateBubble.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/chat/DateBubble.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/DateBubble.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/chat/HostableAvatar.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/HostableAvatar.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/chat/HostableAvatar.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/HostableAvatar.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/chat/MessageNodeDefaults.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/MessageNodeDefaults.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/chat/MessageNodeDefaults.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/MessageNodeDefaults.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/chat/TypingIndicator.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/TypingIndicator.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/chat/TypingIndicator.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/TypingIndicator.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/chat/UnreadSeparator.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/UnreadSeparator.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/chat/UnreadSeparator.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/UnreadSeparator.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/chat/UserAvatar.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/UserAvatar.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/chat/UserAvatar.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/chat/UserAvatar.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/picker/Picker.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/picker/Picker.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/picker/Picker.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/picker/Picker.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/restrictions/RestrictionBlockingView.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/restrictions/RestrictionBlockingView.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/restrictions/RestrictionBlockingView.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/restrictions/RestrictionBlockingView.kt diff --git a/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/AmountAreaPreviews.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/AmountAreaPreviews.kt new file mode 100644 index 0000000000..2dd329962c --- /dev/null +++ b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/AmountAreaPreviews.kt @@ -0,0 +1,38 @@ +package com.getcode.ui.components.text + +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import com.getcode.theme.DesignSystem +import com.getcode.ui.components.R +import com.getcode.utils.network.connectivity.NetworkStateProvider + +private val networkStateValues = NetworkStateProvider().values +@Preview +@Composable +fun AmountPreview() { + DesignSystem { + AmountArea( + amountPrefix = "prefix", + amountText = "$12.34 of Kin", + amountSuffix = "suffix", + captionText = "The value of kin fluctuates", + currencyResId = R.drawable.ic_flag_ca, + networkState = networkStateValues.last(), + ) + } +} + +@Preview +@Composable +fun AmountPreviewDisconnected() { + DesignSystem { + AmountArea( + amountPrefix = "prefix", + amountText = "$12.34 of Kin", + amountSuffix = "suffix", + captionText = "The value of kin fluctuates", + currencyResId = R.drawable.ic_flag_ca, + networkState = networkStateValues.first(), + ) + } +} \ No newline at end of file diff --git a/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/BlurSupport.android.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/BlurSupport.android.kt new file mode 100644 index 0000000000..bc14dc1fbc --- /dev/null +++ b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/BlurSupport.android.kt @@ -0,0 +1,6 @@ +package com.getcode.ui.components.text + +import android.os.Build + +internal actual val SupportsBlurEffect: Boolean + get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/ExpandableText.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/ExpandableText.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/ExpandableText.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/ExpandableText.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/SectionHeader.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/SectionHeader.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/SectionHeader.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/SectionHeader.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/DigitContainer.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/animated/DigitContainer.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/DigitContainer.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/animated/DigitContainer.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/markup/Markup.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/markup/Markup.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/markup/Markup.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/markup/Markup.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/markup/MarkupTextHelper.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/markup/MarkupTextHelper.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/markup/MarkupTextHelper.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/text/markup/MarkupTextHelper.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/toast/FloatingToast.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/toast/FloatingToast.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/toast/FloatingToast.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/toast/FloatingToast.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/toast/FloatingToastHost.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/toast/FloatingToastHost.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/toast/FloatingToastHost.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/toast/FloatingToastHost.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/user/social/SocialUserTitle.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/components/user/social/SocialUserTitle.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/user/social/SocialUserTitle.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/components/user/social/SocialUserTitle.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeBillSwipeToDismiss.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeBillSwipeToDismiss.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeBillSwipeToDismiss.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeBillSwipeToDismiss.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeButton.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeButton.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeButton.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeButton.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeCheckbox.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeCheckbox.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeCheckbox.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeCheckbox.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeKeyPad.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeKeyPad.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeKeyPad.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeKeyPad.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeRadioButton.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeRadioButton.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeRadioButton.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeRadioButton.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeScaffold.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeScaffold.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeScaffold.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeScaffold.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeSeedView.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeSeedView.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeSeedView.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeSeedView.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeSegmentedControl.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeSegmentedControl.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeSegmentedControl.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeSegmentedControl.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeSwitch.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeSwitch.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeSwitch.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/theme/CodeSwitch.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/AnimationUtils.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/AnimationUtils.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/AnimationUtils.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/AnimationUtils.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/ComplementaryGradient.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/ComplementaryGradient.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/ComplementaryGradient.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/ComplementaryGradient.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/EightBitGenerator.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/EightBitGenerator.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/EightBitGenerator.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/EightBitGenerator.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/Geometry.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/Geometry.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/Geometry.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/Geometry.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/KeepScreenOn.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/KeepScreenOn.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/KeepScreenOn.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/KeepScreenOn.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/Keyboard.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/Keyboard.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/Keyboard.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/Keyboard.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/LazyList.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/LazyList.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/LazyList.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/LazyList.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/Maps.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/Maps.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/Maps.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/Maps.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/MeasureScope.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/MeasureScope.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/MeasureScope.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/MeasureScope.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/PaddingValues.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/PaddingValues.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/PaddingValues.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/PaddingValues.kt diff --git a/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/ParseColor.android.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/ParseColor.android.kt new file mode 100644 index 0000000000..8a25261061 --- /dev/null +++ b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/ParseColor.android.kt @@ -0,0 +1,5 @@ +package com.getcode.ui.utils + +import androidx.core.graphics.toColorInt + +internal actual fun parseColorInt(hex: String): Int = hex.toColorInt() diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/PreviewParameters.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/PreviewParameters.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/PreviewParameters.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/PreviewParameters.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/RecompositionHighlighter.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/RecompositionHighlighter.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/RecompositionHighlighter.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/RecompositionHighlighter.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/TextStyle.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/TextStyle.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/TextStyle.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/TextStyle.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/Window.kt b/ui/components/src/androidMain/kotlin/com/getcode/ui/utils/Window.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/Window.kt rename to ui/components/src/androidMain/kotlin/com/getcode/ui/utils/Window.kt diff --git a/ui/components/src/main/res/drawable/ic_arrow_up.xml b/ui/components/src/androidMain/res/drawable/ic_arrow_up.xml similarity index 100% rename from ui/components/src/main/res/drawable/ic_arrow_up.xml rename to ui/components/src/androidMain/res/drawable/ic_arrow_up.xml diff --git a/ui/components/src/main/res/drawable/ic_twitter_verified_badge.xml b/ui/components/src/androidMain/res/drawable/ic_twitter_verified_badge.xml similarity index 100% rename from ui/components/src/main/res/drawable/ic_twitter_verified_badge.xml rename to ui/components/src/androidMain/res/drawable/ic_twitter_verified_badge.xml diff --git a/ui/components/src/main/res/drawable/ic_twitter_verified_badge_gold.xml b/ui/components/src/androidMain/res/drawable/ic_twitter_verified_badge_gold.xml similarity index 100% rename from ui/components/src/main/res/drawable/ic_twitter_verified_badge_gold.xml rename to ui/components/src/androidMain/res/drawable/ic_twitter_verified_badge_gold.xml diff --git a/ui/components/src/main/res/drawable/ic_twitter_verified_badge_gray.xml b/ui/components/src/androidMain/res/drawable/ic_twitter_verified_badge_gray.xml similarity index 100% rename from ui/components/src/main/res/drawable/ic_twitter_verified_badge_gray.xml rename to ui/components/src/androidMain/res/drawable/ic_twitter_verified_badge_gray.xml diff --git a/ui/components/src/main/res/values/strings.xml b/ui/components/src/androidMain/res/values/strings.xml similarity index 100% rename from ui/components/src/main/res/values/strings.xml rename to ui/components/src/androidMain/res/values/strings.xml diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/Badge.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/Badge.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/Badge.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/Badge.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/ConnectionStatusComponent.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/ConnectionStatusComponent.kt similarity index 82% rename from ui/components/src/main/kotlin/com/getcode/ui/components/ConnectionStatusComponent.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/ConnectionStatusComponent.kt index 20d932c5cd..1f2dc307b3 100644 --- a/ui/components/src/main/kotlin/com/getcode/ui/components/ConnectionStatusComponent.kt +++ b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/ConnectionStatusComponent.kt @@ -7,13 +7,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter import com.getcode.theme.CodeTheme import com.getcode.ui.theme.CodeCircularProgressIndicator import com.getcode.utils.network.ConnectionType import com.getcode.utils.network.NetworkState -import com.getcode.utils.network.connectivity.NetworkStateProvider @Composable fun ConnectionStatus(state: NetworkState, modifier: Modifier = Modifier) { @@ -42,10 +39,3 @@ fun ConnectionStatus(state: NetworkState, modifier: Modifier = Modifier) { } } } - - -@Preview -@Composable -fun ConnectionReconnectingPreview(@PreviewParameter(NetworkStateProvider::class) state: NetworkState) { - ConnectionStatus(state) -} \ No newline at end of file diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/glass/FloatingGlass.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/glass/FloatingGlass.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/glass/FloatingGlass.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/glass/FloatingGlass.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountArea.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountArea.kt similarity index 86% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountArea.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountArea.kt index 90127a695d..a104709340 100644 --- a/ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountArea.kt +++ b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountArea.kt @@ -17,7 +17,10 @@ import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.painterResource +import org.jetbrains.compose.resources.painterResource +import com.getcode.util.resources.Res +import com.getcode.util.resources.painterForResId +import com.getcode.util.resources.ic_kin_brand import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -26,12 +29,10 @@ import com.getcode.theme.DesignSystem import com.getcode.theme.White import com.getcode.theme.bolded import com.getcode.ui.components.ConnectionStatus -import com.getcode.ui.components.R import androidx.compose.foundation.clickable import com.getcode.ui.utils.ConstraintMode import com.getcode.utils.network.LocalNetworkObserver import com.getcode.utils.network.NetworkState -import com.getcode.utils.network.connectivity.NetworkStateProvider @OptIn(ExperimentalAnimationApi::class) @Composable @@ -145,7 +146,7 @@ private fun ValueHint( modifier = Modifier .padding(end = CodeTheme.dimens.staticGrid.x1) .requiredSize(CodeTheme.dimens.staticGrid.x2), - painter = painterResource(R.drawable.ic_kin_brand), + painter = painterResource(Res.drawable.ic_kin_brand), colorFilter = ColorFilter.tint(iconColor), contentDescription = "" ) @@ -163,34 +164,3 @@ private fun ValueHint( } } } - -private val networkStateValues = NetworkStateProvider().values -@Preview -@Composable -fun AmountPreview() { - DesignSystem { - AmountArea( - amountPrefix = "prefix", - amountText = "$12.34 of Kin", - amountSuffix = "suffix", - captionText = "The value of kin fluctuates", - currencyResId = R.drawable.ic_flag_ca, - networkState = networkStateValues.last(), - ) - } -} - -@Preview -@Composable -fun AmountPreviewDisconnected() { - DesignSystem { - AmountArea( - amountPrefix = "prefix", - amountText = "$12.34 of Kin", - amountSuffix = "suffix", - captionText = "The value of kin fluctuates", - currencyResId = R.drawable.ic_flag_ca, - networkState = networkStateValues.first(), - ) - } -} \ No newline at end of file diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountText.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountText.kt similarity index 93% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountText.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountText.kt index b2813fb856..e1b98f70b2 100644 --- a/ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountText.kt +++ b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountText.kt @@ -23,7 +23,10 @@ import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.res.painterResource +import org.jetbrains.compose.resources.painterResource +import com.getcode.util.resources.Res +import com.getcode.util.resources.painterForResId +import com.getcode.util.resources.ic_dropdown import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign @@ -31,7 +34,6 @@ import androidx.compose.ui.unit.TextUnit import com.getcode.theme.CodeTheme import com.getcode.theme.White import com.getcode.theme.bolded -import com.getcode.ui.components.R @Composable internal fun RowScope.AmountLeadingIcons( @@ -44,7 +46,7 @@ internal fun RowScope.AmountLeadingIcons( .align(CenterVertically) .requiredSize(CodeTheme.dimens.staticGrid.x7) .clip(CircleShape), - painter = painterResource(currencyResId), + painter = painterForResId(currencyResId), contentDescription = "" ) } @@ -54,7 +56,7 @@ internal fun RowScope.AmountLeadingIcons( .padding(end = CodeTheme.dimens.grid.x2) .requiredSize(CodeTheme.dimens.grid.x5) .align(CenterVertically), - painter = painterResource(R.drawable.ic_dropdown), + painter = painterResource(Res.drawable.ic_dropdown), contentDescription = "" ) } else { diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountTextAnimated.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountTextAnimated.kt similarity index 97% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountTextAnimated.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountTextAnimated.kt index 4ef661fbc5..4e1ffb9c01 100644 --- a/ui/components/src/main/kotlin/com/getcode/ui/components/text/AmountTextAnimated.kt +++ b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AmountTextAnimated.kt @@ -40,7 +40,10 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource +import org.jetbrains.compose.resources.painterResource +import com.getcode.util.resources.Res +import com.getcode.util.resources.painterForResId +import com.getcode.util.resources.ic_dropdown import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -50,7 +53,6 @@ import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.getcode.theme.CodeTheme -import com.getcode.ui.components.R import com.getcode.ui.components.text.NumberInputHelper.Companion.DECIMAL_SEPARATOR import com.getcode.ui.components.text.NumberInputHelper.Companion.GROUPING_SEPARATOR import com.getcode.ui.components.text.animated.AnimatedPlaceholderDigit @@ -310,14 +312,14 @@ internal fun AmountTextAnimated( modifier = Modifier .requiredSize(CodeTheme.dimens.grid.x7) .clip(CircleShape), - painter = painterResource(currencyResId), + painter = painterForResId(currencyResId), contentDescription = "" ) if (isClickable) { Image( modifier = Modifier .width(CodeTheme.dimens.grid.x4), - painter = painterResource(R.drawable.ic_dropdown), + painter = painterResource(Res.drawable.ic_dropdown), contentDescription = "" ) } else { diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/AnimatedNumberText.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AnimatedNumberText.kt similarity index 99% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/AnimatedNumberText.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AnimatedNumberText.kt index 775b476850..b1f20c6b5c 100644 --- a/ui/components/src/main/kotlin/com/getcode/ui/components/text/AnimatedNumberText.kt +++ b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/AnimatedNumberText.kt @@ -1,6 +1,5 @@ package com.getcode.ui.components.text -import android.os.Build import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.FloatSpringSpec import androidx.compose.animation.core.tween @@ -418,7 +417,7 @@ private fun GraphicsLayerScope.applyGlyphMotion(d: Float, fade: Float, p: Float, // The layer carries [MaxBlur] of padding on each side; travel is in heights of the glyph itself. translationY = motion.travel * (size.height - 2 * MaxBlur.toPx()) alpha = motion.alpha - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (SupportsBlurEffect) { val radius = MaxBlur.toPx() * motion.blur renderEffect = if (radius > 0.5f) BlurEffect(radius, radius, TileMode.Decal) else null } diff --git a/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/BlurSupport.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/BlurSupport.kt new file mode 100644 index 0000000000..607ca47c5e --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/BlurSupport.kt @@ -0,0 +1,4 @@ +package com.getcode.ui.components.text + +/** Whether a graphics layer can take a blur `RenderEffect` (API 31+ on Android). */ +internal expect val SupportsBlurEffect: Boolean diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/NumberInputHelper.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/NumberInputHelper.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/NumberInputHelper.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/NumberInputHelper.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/NumberRollMotion.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/NumberRollMotion.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/NumberRollMotion.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/NumberRollMotion.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/AnimatedPlaceholderDigit.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/animated/AnimatedPlaceholderDigit.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/AnimatedPlaceholderDigit.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/animated/AnimatedPlaceholderDigit.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/Digit.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/animated/Digit.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/Digit.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/animated/Digit.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/DigitAnimations.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/animated/DigitAnimations.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/DigitAnimations.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/animated/DigitAnimations.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/NormalizedPrefixText.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/animated/NormalizedPrefixText.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/components/text/animated/NormalizedPrefixText.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/components/text/animated/NormalizedPrefixText.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/theme/CodeCircularProgressIndicator.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/theme/CodeCircularProgressIndicator.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/theme/CodeCircularProgressIndicator.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/theme/CodeCircularProgressIndicator.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/AutoSizeText.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/utils/AutoSizeText.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/AutoSizeText.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/utils/AutoSizeText.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/AutoSizeTextMeasurer.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/utils/AutoSizeTextMeasurer.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/AutoSizeTextMeasurer.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/utils/AutoSizeTextMeasurer.kt diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/ColorTransformations.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/utils/ColorTransformations.kt similarity index 98% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/ColorTransformations.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/utils/ColorTransformations.kt index 53871673e6..7cc6c3371c 100644 --- a/ui/components/src/main/kotlin/com/getcode/ui/utils/ColorTransformations.kt +++ b/ui/components/src/commonMain/kotlin/com/getcode/ui/utils/ColorTransformations.kt @@ -2,7 +2,6 @@ package com.getcode.ui.utils import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import androidx.core.graphics.toColorInt import kotlin.math.abs import kotlin.math.roundToInt @@ -178,7 +177,7 @@ private fun invertColor(color: Color): Color { } fun hexToColor(hex: String): Color { - return Color(hex.toColorInt()) + return Color(parseColorInt(hex)) } fun adjustBrightness(color: Color, factor: Float): Color { diff --git a/ui/components/src/commonMain/kotlin/com/getcode/ui/utils/ParseColor.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/utils/ParseColor.kt new file mode 100644 index 0000000000..4328c1ddb2 --- /dev/null +++ b/ui/components/src/commonMain/kotlin/com/getcode/ui/utils/ParseColor.kt @@ -0,0 +1,4 @@ +package com.getcode.ui.utils + +/** Parses a `#RRGGBB` or `#AARRGGBB` color string into an ARGB int. */ +internal expect fun parseColorInt(hex: String): Int diff --git a/ui/components/src/main/kotlin/com/getcode/ui/utils/TextUnit.kt b/ui/components/src/commonMain/kotlin/com/getcode/ui/utils/TextUnit.kt similarity index 100% rename from ui/components/src/main/kotlin/com/getcode/ui/utils/TextUnit.kt rename to ui/components/src/commonMain/kotlin/com/getcode/ui/utils/TextUnit.kt diff --git a/ui/components/src/jvmMain/kotlin/com/getcode/ui/components/text/BlurSupport.jvm.kt b/ui/components/src/jvmMain/kotlin/com/getcode/ui/components/text/BlurSupport.jvm.kt new file mode 100644 index 0000000000..4a97f4bd15 --- /dev/null +++ b/ui/components/src/jvmMain/kotlin/com/getcode/ui/components/text/BlurSupport.jvm.kt @@ -0,0 +1,3 @@ +package com.getcode.ui.components.text + +internal actual val SupportsBlurEffect: Boolean = true diff --git a/ui/components/src/jvmMain/kotlin/com/getcode/ui/utils/ParseColor.jvm.kt b/ui/components/src/jvmMain/kotlin/com/getcode/ui/utils/ParseColor.jvm.kt new file mode 100644 index 0000000000..27901571c4 --- /dev/null +++ b/ui/components/src/jvmMain/kotlin/com/getcode/ui/utils/ParseColor.jvm.kt @@ -0,0 +1,11 @@ +package com.getcode.ui.utils + +internal actual fun parseColorInt(hex: String): Int { + require(hex.startsWith("#")) { "Unknown color: $hex" } + val value = hex.substring(1).toLong(16) + return when (hex.length) { + 7 -> (value or 0xFF000000).toInt() + 9 -> value.toInt() + else -> throw IllegalArgumentException("Unknown color: $hex") + } +} diff --git a/ui/core/build.gradle.kts b/ui/core/build.gradle.kts index 127ea489a5..5e0a924f9d 100644 --- a/ui/core/build.gradle.kts +++ b/ui/core/build.gradle.kts @@ -1,17 +1,30 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) } -android { - namespace = "${Gradle.codeNamespace}.ui.core" -} +kotlin { + android { + namespace = "${Gradle.codeNamespace}.ui.core" + androidResources { enable = true } + } -dependencies { - implementation(libs.compose.animation) - implementation(libs.compose.activities) - implementation(libs.compose.material) - implementation(libs.kotlinx.serialization.core) - api(project(":ui:resources")) - api(project(":ui:testing")) - implementation(project(":ui:theme")) + sourceSets { + commonMain.dependencies { + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.material) + implementation(libs.kotlinx.serialization.core) + api(project(":ui:resources")) + implementation(project(":ui:theme")) + } + androidMain.dependencies { + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(libs.compose.animation) + implementation(libs.compose.activities) + implementation(libs.compose.material) + api(project(":ui:testing")) + } + } } diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/AnimationScale.kt b/ui/core/src/androidMain/kotlin/com/getcode/ui/core/AnimationScale.kt similarity index 100% rename from ui/core/src/main/kotlin/com/getcode/ui/core/AnimationScale.kt rename to ui/core/src/androidMain/kotlin/com/getcode/ui/core/AnimationScale.kt diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/ContextMenuAction.kt b/ui/core/src/androidMain/kotlin/com/getcode/ui/core/ContextMenuAction.kt similarity index 100% rename from ui/core/src/main/kotlin/com/getcode/ui/core/ContextMenuAction.kt rename to ui/core/src/androidMain/kotlin/com/getcode/ui/core/ContextMenuAction.kt diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/ExpandedTouchTarget.kt b/ui/core/src/androidMain/kotlin/com/getcode/ui/core/ExpandedTouchTarget.kt similarity index 100% rename from ui/core/src/main/kotlin/com/getcode/ui/core/ExpandedTouchTarget.kt rename to ui/core/src/androidMain/kotlin/com/getcode/ui/core/ExpandedTouchTarget.kt diff --git a/ui/core/src/androidMain/kotlin/com/getcode/ui/core/Modifier.android.kt b/ui/core/src/androidMain/kotlin/com/getcode/ui/core/Modifier.android.kt new file mode 100644 index 0000000000..ff66150c23 --- /dev/null +++ b/ui/core/src/androidMain/kotlin/com/getcode/ui/core/Modifier.android.kt @@ -0,0 +1,15 @@ +package com.getcode.ui.core + +import android.view.MotionEvent +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.pointerInteropFilter + +@OptIn(ExperimentalComposeUiApi::class) +fun Modifier.swallowClicks(onClick: () -> Unit = { }): Modifier = + this.pointerInteropFilter { + if (it.actionMasked == MotionEvent.ACTION_UP) { + onClick() + } + true + } diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/StarMaskVisualTransformation.kt b/ui/core/src/androidMain/kotlin/com/getcode/ui/core/StarMaskVisualTransformation.kt similarity index 100% rename from ui/core/src/main/kotlin/com/getcode/ui/core/StarMaskVisualTransformation.kt rename to ui/core/src/androidMain/kotlin/com/getcode/ui/core/StarMaskVisualTransformation.kt diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/visibility/VisibilityInfo.kt b/ui/core/src/androidMain/kotlin/com/getcode/ui/core/visibility/VisibilityInfo.kt similarity index 100% rename from ui/core/src/main/kotlin/com/getcode/ui/core/visibility/VisibilityInfo.kt rename to ui/core/src/androidMain/kotlin/com/getcode/ui/core/visibility/VisibilityInfo.kt diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/visibility/VisibilityTrackerNode.kt b/ui/core/src/androidMain/kotlin/com/getcode/ui/core/visibility/VisibilityTrackerNode.kt similarity index 100% rename from ui/core/src/main/kotlin/com/getcode/ui/core/visibility/VisibilityTrackerNode.kt rename to ui/core/src/androidMain/kotlin/com/getcode/ui/core/visibility/VisibilityTrackerNode.kt diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/IntSize.kt b/ui/core/src/commonMain/kotlin/com/getcode/ui/core/IntSize.kt similarity index 100% rename from ui/core/src/main/kotlin/com/getcode/ui/core/IntSize.kt rename to ui/core/src/commonMain/kotlin/com/getcode/ui/core/IntSize.kt diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/Modifier.kt b/ui/core/src/commonMain/kotlin/com/getcode/ui/core/Modifier.kt similarity index 98% rename from ui/core/src/main/kotlin/com/getcode/ui/core/Modifier.kt rename to ui/core/src/commonMain/kotlin/com/getcode/ui/core/Modifier.kt index b4f1e39ebe..70b3cf176c 100644 --- a/ui/core/src/main/kotlin/com/getcode/ui/core/Modifier.kt +++ b/ui/core/src/commonMain/kotlin/com/getcode/ui/core/Modifier.kt @@ -1,6 +1,5 @@ package com.getcode.ui.core -import android.view.MotionEvent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Indication import androidx.compose.foundation.border @@ -42,7 +41,6 @@ import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.takeOrElse -import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.platform.LocalDensity @@ -139,15 +137,6 @@ fun Modifier.measured(block: (DpSize) -> Unit): Modifier = composed { fun Modifier.measuredIntSize(block: (IntSize) -> Unit): Modifier = onPlaced { block(it.size) } -@OptIn(ExperimentalComposeUiApi::class) -fun Modifier.swallowClicks(onClick: () -> Unit = { }): Modifier = - this.pointerInteropFilter { - if (it.actionMasked == MotionEvent.ACTION_UP) { - onClick() - } - true - } - /** * Draws circle with a solid [color] behind the content. * diff --git a/ui/core/src/main/kotlin/com/getcode/ui/core/RestrictionType.kt b/ui/core/src/commonMain/kotlin/com/getcode/ui/core/RestrictionType.kt similarity index 100% rename from ui/core/src/main/kotlin/com/getcode/ui/core/RestrictionType.kt rename to ui/core/src/commonMain/kotlin/com/getcode/ui/core/RestrictionType.kt diff --git a/ui/resources/src/androidMain/kotlin/com/getcode/util/resources/PainterForResId.android.kt b/ui/resources/src/androidMain/kotlin/com/getcode/util/resources/PainterForResId.android.kt new file mode 100644 index 0000000000..f64906572b --- /dev/null +++ b/ui/resources/src/androidMain/kotlin/com/getcode/util/resources/PainterForResId.android.kt @@ -0,0 +1,8 @@ +package com.getcode.util.resources + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource + +@Composable +actual fun painterForResId(id: Int): Painter = painterResource(id) diff --git a/ui/resources/src/commonMain/kotlin/com/getcode/util/resources/PainterForResId.kt b/ui/resources/src/commonMain/kotlin/com/getcode/util/resources/PainterForResId.kt new file mode 100644 index 0000000000..25cd6a885d --- /dev/null +++ b/ui/resources/src/commonMain/kotlin/com/getcode/util/resources/PainterForResId.kt @@ -0,0 +1,14 @@ +package com.getcode.util.resources + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter + +/** + * Loads the drawable behind a resource ID handed out by [ResourceHelper.getIdentifier]. + * + * Shared components take currency and flag icons as `Int` IDs; on Android these are `R.drawable` + * values, on desktop they index the generated Compose resources. + */ +@Composable +expect fun painterForResId(@DrawableRes id: Int): Painter diff --git a/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceHelper.kt b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceHelper.kt new file mode 100644 index 0000000000..c55ed631b5 --- /dev/null +++ b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceHelper.kt @@ -0,0 +1,33 @@ +package com.getcode.util.resources + +import kotlinx.coroutines.runBlocking +import org.jetbrains.compose.resources.getPluralString +import org.jetbrains.compose.resources.getString + +/** + * Desktop [ResourceHelper] over the Compose resources generated from `ui:resources`' Android + * `res/`. IDs come from [ComposeResourceIds]; dimensions and raw resources are not mapped. + */ +class ComposeResourceHelper : ResourceHelper { + override fun getString(resourceId: Int): String = + runBlocking { getString(ComposeResourceIds.string(resourceId)) } + + override fun getString(resourceId: Int, vararg formatArgs: Any): String = + runBlocking { getString(ComposeResourceIds.string(resourceId), *formatArgs) } + + override fun getRawResource(resourceId: Int): String = "" + + override fun getQuantityString(id: Int, quantity: Int, vararg formatArgs: Any, default: String): String = + runCatching { + runBlocking { getPluralString(ComposeResourceIds.plural(id), quantity, *formatArgs) } + }.getOrDefault(default) + + override fun getDimension(dimenId: Int, default: Float): Float = default + + override fun getDimensionPixelSize(dimenId: Int, default: Int): Int = default + + override fun getIdentifier(name: String, type: ResourceType): Int? = when (type) { + ResourceType.Drawable -> ComposeResourceIds.drawableId(name) + ResourceType.String -> ComposeResourceIds.stringId(name) + } +} diff --git a/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt new file mode 100644 index 0000000000..898ed8033f --- /dev/null +++ b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt @@ -0,0 +1,34 @@ +package com.getcode.util.resources + +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.StringResource + +/** + * Stable `Int` IDs for the generated Compose resources, so desktop can serve the `Int`-keyed + * [ResourceHelper] API. Each resource type gets its own range, ordered by resource name. + */ +object ComposeResourceIds { + private const val DRAWABLE_BASE = 0x7f010000 + private const val STRING_BASE = 0x7f020000 + private const val PLURAL_BASE = 0x7f030000 + + private val drawables: List> by lazy { Res.allDrawableResources.toSortedList() } + private val strings: List> by lazy { Res.allStringResources.toSortedList() } + private val plurals: List> by lazy { Res.allPluralStringResources.toSortedList() } + + fun drawableId(name: String): Int? = drawables.indexOfName(name)?.plus(DRAWABLE_BASE) + fun stringId(name: String): Int? = strings.indexOfName(name)?.plus(STRING_BASE) + + fun drawable(id: Int): DrawableResource = drawables.at(id - DRAWABLE_BASE, id) + fun string(id: Int): StringResource = strings.at(id - STRING_BASE, id) + fun plural(id: Int): PluralStringResource = plurals.at(id - PLURAL_BASE, id) + + private fun Map.toSortedList() = entries.sortedBy { it.key }.map { it.key to it.value } + + private fun List>.indexOfName(name: String): Int? = + indexOfFirst { it.first == name }.takeIf { it >= 0 } + + private fun List>.at(index: Int, id: Int): T = + getOrNull(index)?.second ?: throw IllegalArgumentException("No Compose resource for ID 0x${id.toString(16)}") +} diff --git a/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/PainterForResId.jvm.kt b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/PainterForResId.jvm.kt new file mode 100644 index 0000000000..0076b5a5dd --- /dev/null +++ b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/PainterForResId.jvm.kt @@ -0,0 +1,8 @@ +package com.getcode.util.resources + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.painter.Painter +import org.jetbrains.compose.resources.painterResource + +@Composable +actual fun painterForResId(id: Int): Painter = painterResource(ComposeResourceIds.drawable(id)) From 80adf96d5c011520fd4e5c4049db73ae90121628 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:23:31 -0400 Subject: [PATCH 04/17] build(navigation): move ui:navigation to android+jvm KMP CodeNavigator, its options, flow scope and the nav-result types move to commonMain; navigation3 1.1.4 publishes a desktop variant, so the back stack type is the same artifact on both platforms. Scenes, flow hosts and AppNavHost stay in androidMain. RepeatOnLifecycle reads LocalLifecycleOwner from lifecycle-runtime-compose, the class the old androidx.compose.ui.platform one already aliases on Android. --- gradle/libs.versions.toml | 3 + ui/navigation/build.gradle.kts | 77 +++++++++++-------- .../kotlin/com/getcode/navigation/TestNav.kt | 0 .../core/CodeNavigatorDispatchTest.kt | 0 .../core/CodeNavigatorHarnessTest.kt | 0 .../core/CodeNavigatorIntentTest.kt | 0 .../decorators/RetentionLedgerTest.kt | 0 .../navigation/flow/InnerFlowNavigatorTest.kt | 0 .../scenes/ModalBottomSheetSceneTest.kt | 0 .../com/getcode/navigation/AppNavHost.kt | 0 .../com/getcode/navigation/NavMetadata.kt | 2 +- .../NavResultScopeEntryDecorator.kt | 0 .../decorators/RetainedEntryState.kt | 0 .../extensions/ViewModelExtensions.kt | 0 .../navigation/flow/FlowDismissStyle.kt | 0 .../com/getcode/navigation/flow/FlowHost.kt | 2 +- .../getcode/navigation/flow/FlowNavigator.kt | 2 +- .../com/getcode/navigation/flow/FlowRoute.kt | 2 +- .../getcode/navigation/flow/FlowViewModel.kt | 0 .../scenes/FullscreenOverlaySceneStrategy.kt | 0 .../scenes/ModalBottomSheetSceneStrategy.kt | 2 +- .../com/getcode/navigation/scrim/Scrim.kt | 0 .../getcode/ui/utils/DisableSheetGestures.kt | 0 .../com/getcode/ui/utils/SheetExpansion.kt | 0 .../ui/utils/SheetResignmentModifierNode.kt | 0 .../kotlin/com/getcode/view/BaseViewModel.kt | 0 .../com/getcode/navigation/NavContentKey.kt | 0 .../kotlin/com/getcode/navigation/Types.kt | 0 .../getcode/navigation/core/CodeNavigator.kt | 6 +- .../com/getcode/navigation/core/FlowScope.kt | 2 +- .../com/getcode/navigation/core/NavOptions.kt | 4 +- .../com/getcode/navigation/flow/FlowStep.kt | 0 .../getcode/navigation/results/NavResult.kt | 4 +- .../navigation/results/NavResultController.kt | 2 +- .../navigation/results/NavResultKey.kt | 2 +- .../navigation/results/NavResultStore.kt | 2 +- .../com/getcode/navigation/results/RetVal.kt | 2 +- .../navigation/utils/KClassSerializer.kt | 0 .../utils/lifecycle/RepeatOnLifecycle.kt | 2 +- 39 files changed, 67 insertions(+), 49 deletions(-) rename ui/navigation/src/{test => androidHostTest}/kotlin/com/getcode/navigation/TestNav.kt (100%) rename ui/navigation/src/{test => androidHostTest}/kotlin/com/getcode/navigation/core/CodeNavigatorDispatchTest.kt (100%) rename ui/navigation/src/{test => androidHostTest}/kotlin/com/getcode/navigation/core/CodeNavigatorHarnessTest.kt (100%) rename ui/navigation/src/{test => androidHostTest}/kotlin/com/getcode/navigation/core/CodeNavigatorIntentTest.kt (100%) rename ui/navigation/src/{test => androidHostTest}/kotlin/com/getcode/navigation/decorators/RetentionLedgerTest.kt (100%) rename ui/navigation/src/{test => androidHostTest}/kotlin/com/getcode/navigation/flow/InnerFlowNavigatorTest.kt (100%) rename ui/navigation/src/{test => androidHostTest}/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneTest.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/AppNavHost.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/NavMetadata.kt (99%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/decorators/NavResultScopeEntryDecorator.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/decorators/RetainedEntryState.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/extensions/ViewModelExtensions.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/flow/FlowDismissStyle.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/flow/FlowHost.kt (99%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/flow/FlowNavigator.kt (98%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/flow/FlowRoute.kt (97%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/flow/FlowViewModel.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/scenes/FullscreenOverlaySceneStrategy.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneStrategy.kt (99%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/navigation/scrim/Scrim.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/ui/utils/DisableSheetGestures.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/ui/utils/SheetExpansion.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/ui/utils/SheetResignmentModifierNode.kt (100%) rename ui/navigation/src/{main => androidMain}/kotlin/com/getcode/view/BaseViewModel.kt (100%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/NavContentKey.kt (100%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/Types.kt (100%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/core/CodeNavigator.kt (98%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/core/FlowScope.kt (96%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/core/NavOptions.kt (89%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/flow/FlowStep.kt (100%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/results/NavResult.kt (90%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/results/NavResultController.kt (98%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/results/NavResultKey.kt (94%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/results/NavResultStore.kt (99%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/results/RetVal.kt (91%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/utils/KClassSerializer.kt (100%) rename ui/navigation/src/{main => commonMain}/kotlin/com/getcode/navigation/utils/lifecycle/RepeatOnLifecycle.kt (94%) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b8b9612939..a21628234f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -148,6 +148,9 @@ androidx-exifinterface = { module = "androidx.exifinterface:exifinterface", vers androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } +androidx-lifecycle-runtime-common = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "androidx-lifecycle" } +androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "androidx-lifecycle" } navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "navigation3" } navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "navigation3" } lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "navigation3-lifecycle" } diff --git a/ui/navigation/build.gradle.kts b/ui/navigation/build.gradle.kts index 6e8e974f1f..b124afb989 100644 --- a/ui/navigation/build.gradle.kts +++ b/ui/navigation/build.gradle.kts @@ -1,40 +1,55 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) - alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.ksp) - alias(libs.plugins.hilt) id("org.jetbrains.kotlin.plugin.parcelize") } -android { - namespace = "${Gradle.codeNamespace}.navigation" - buildFeatures { buildConfig = true } -} - -dependencies { - api(project(":libs:coroutines")) - implementation(project(":libs:logging")) - implementation(project(":ui:core")) - api(project(":ui:resources")) - implementation(project(":ui:theme")) +kotlin { + android { + namespace = "${Gradle.codeNamespace}.navigation" + } - implementation(libs.compose.material3) - implementation(libs.compose.unstyled.bottom.sheet) - implementation(libs.compose.activities) - implementation(libs.bundles.kotlinx.serialization) - implementation(libs.kotlin.reflect) + sourceSets { + commonMain.dependencies { + api(project(":libs:coroutines")) + implementation(project(":libs:logging")) + implementation(project(":libs:parcelable")) + implementation(project(":ui:core")) + api(project(":ui:resources")) + implementation(project(":ui:theme")) - implementation(libs.bundles.hilt) - ksp(libs.bundles.hilt.compiler) - - api(libs.compose.view.models) - api(libs.navigation3.runtime) - api(libs.navigation3.ui) - api(libs.lifecycle.viewmodel.navigation3) - api(libs.hilt.nav.compose) - api(libs.rinku) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.bundles.kotlinx.serialization) + implementation(libs.kotlin.reflect) + implementation(libs.androidx.lifecycle.runtime.common) + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.androidx.lifecycle.viewmodel.savedstate) + api(libs.compose.view.models) + api(libs.navigation3.runtime) + } + androidMain.dependencies { + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(libs.compose.material3) + implementation(libs.compose.unstyled.bottom.sheet) + implementation(libs.compose.activities) + implementation(libs.bundles.hilt) + api(libs.navigation3.ui) + api(libs.lifecycle.viewmodel.navigation3) + api(libs.hilt.nav.compose) + api(libs.rinku) + } + getByName("androidHostTest").dependencies { + implementation(kotlin("test")) + implementation(libs.bundles.unit.testing) + implementation(libs.bundles.compose.ui.testing) + } + } +} - testImplementation(kotlin("test")) - testImplementation(libs.bundles.unit.testing) - testImplementation(libs.bundles.compose.ui.testing) +dependencies { + add("kspAndroid", libs.hilt.android.compiler) + add("kspAndroid", libs.hilt.compiler) } diff --git a/ui/navigation/src/test/kotlin/com/getcode/navigation/TestNav.kt b/ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/TestNav.kt similarity index 100% rename from ui/navigation/src/test/kotlin/com/getcode/navigation/TestNav.kt rename to ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/TestNav.kt diff --git a/ui/navigation/src/test/kotlin/com/getcode/navigation/core/CodeNavigatorDispatchTest.kt b/ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/core/CodeNavigatorDispatchTest.kt similarity index 100% rename from ui/navigation/src/test/kotlin/com/getcode/navigation/core/CodeNavigatorDispatchTest.kt rename to ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/core/CodeNavigatorDispatchTest.kt diff --git a/ui/navigation/src/test/kotlin/com/getcode/navigation/core/CodeNavigatorHarnessTest.kt b/ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/core/CodeNavigatorHarnessTest.kt similarity index 100% rename from ui/navigation/src/test/kotlin/com/getcode/navigation/core/CodeNavigatorHarnessTest.kt rename to ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/core/CodeNavigatorHarnessTest.kt diff --git a/ui/navigation/src/test/kotlin/com/getcode/navigation/core/CodeNavigatorIntentTest.kt b/ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/core/CodeNavigatorIntentTest.kt similarity index 100% rename from ui/navigation/src/test/kotlin/com/getcode/navigation/core/CodeNavigatorIntentTest.kt rename to ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/core/CodeNavigatorIntentTest.kt diff --git a/ui/navigation/src/test/kotlin/com/getcode/navigation/decorators/RetentionLedgerTest.kt b/ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/decorators/RetentionLedgerTest.kt similarity index 100% rename from ui/navigation/src/test/kotlin/com/getcode/navigation/decorators/RetentionLedgerTest.kt rename to ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/decorators/RetentionLedgerTest.kt diff --git a/ui/navigation/src/test/kotlin/com/getcode/navigation/flow/InnerFlowNavigatorTest.kt b/ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/flow/InnerFlowNavigatorTest.kt similarity index 100% rename from ui/navigation/src/test/kotlin/com/getcode/navigation/flow/InnerFlowNavigatorTest.kt rename to ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/flow/InnerFlowNavigatorTest.kt diff --git a/ui/navigation/src/test/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneTest.kt b/ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneTest.kt similarity index 100% rename from ui/navigation/src/test/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneTest.kt rename to ui/navigation/src/androidHostTest/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneTest.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/AppNavHost.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/AppNavHost.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/AppNavHost.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/AppNavHost.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/NavMetadata.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/NavMetadata.kt similarity index 99% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/NavMetadata.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/NavMetadata.kt index 1718fc1334..cda74df70b 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/NavMetadata.kt +++ b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/NavMetadata.kt @@ -1,6 +1,6 @@ package com.getcode.navigation -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/decorators/NavResultScopeEntryDecorator.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/decorators/NavResultScopeEntryDecorator.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/decorators/NavResultScopeEntryDecorator.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/decorators/NavResultScopeEntryDecorator.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/decorators/RetainedEntryState.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/decorators/RetainedEntryState.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/decorators/RetainedEntryState.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/decorators/RetainedEntryState.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/extensions/ViewModelExtensions.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/extensions/ViewModelExtensions.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/extensions/ViewModelExtensions.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/extensions/ViewModelExtensions.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowDismissStyle.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowDismissStyle.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowDismissStyle.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowDismissStyle.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowHost.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowHost.kt similarity index 99% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowHost.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowHost.kt index 13bb0ed33f..e589b0f90c 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowHost.kt +++ b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowHost.kt @@ -1,6 +1,6 @@ package com.getcode.navigation.flow -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform import androidx.compose.animation.EnterTransition diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowNavigator.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowNavigator.kt similarity index 98% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowNavigator.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowNavigator.kt index 0f493b9796..ceb3485c13 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowNavigator.kt +++ b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowNavigator.kt @@ -1,6 +1,6 @@ package com.getcode.navigation.flow -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.compose.runtime.Composable import androidx.compose.runtime.staticCompositionLocalOf import androidx.navigation3.runtime.NavKey diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowRoute.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowRoute.kt similarity index 97% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowRoute.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowRoute.kt index a3fab98589..49bee9ba82 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowRoute.kt +++ b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowRoute.kt @@ -1,6 +1,6 @@ package com.getcode.navigation.flow -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.navigation3.runtime.NavKey diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowViewModel.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowViewModel.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowViewModel.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowViewModel.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/scenes/FullscreenOverlaySceneStrategy.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/scenes/FullscreenOverlaySceneStrategy.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/scenes/FullscreenOverlaySceneStrategy.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/scenes/FullscreenOverlaySceneStrategy.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneStrategy.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneStrategy.kt similarity index 99% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneStrategy.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneStrategy.kt index c792fd1e50..d9ce8f8b86 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneStrategy.kt +++ b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/scenes/ModalBottomSheetSceneStrategy.kt @@ -1,7 +1,7 @@ package com.getcode.navigation.scenes import android.os.Build -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.activity.compose.BackHandler import androidx.compose.animation.SharedTransitionLayout import androidx.compose.foundation.background diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/scrim/Scrim.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/scrim/Scrim.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/scrim/Scrim.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/navigation/scrim/Scrim.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/ui/utils/DisableSheetGestures.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/ui/utils/DisableSheetGestures.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/ui/utils/DisableSheetGestures.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/ui/utils/DisableSheetGestures.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/ui/utils/SheetExpansion.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/ui/utils/SheetExpansion.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/ui/utils/SheetExpansion.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/ui/utils/SheetExpansion.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/ui/utils/SheetResignmentModifierNode.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/ui/utils/SheetResignmentModifierNode.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/ui/utils/SheetResignmentModifierNode.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/ui/utils/SheetResignmentModifierNode.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/view/BaseViewModel.kt b/ui/navigation/src/androidMain/kotlin/com/getcode/view/BaseViewModel.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/view/BaseViewModel.kt rename to ui/navigation/src/androidMain/kotlin/com/getcode/view/BaseViewModel.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/NavContentKey.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/NavContentKey.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/NavContentKey.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/NavContentKey.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/Types.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/Types.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/Types.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/Types.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/core/CodeNavigator.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/CodeNavigator.kt similarity index 98% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/core/CodeNavigator.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/CodeNavigator.kt index e609baf033..9f29459ee3 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/core/CodeNavigator.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/CodeNavigator.kt @@ -21,7 +21,7 @@ import com.getcode.navigation.results.SavedStateHandleNavResultController import com.getcode.navigation.results.rememberNavResultStore import com.getcode.utils.TraceType import com.getcode.utils.trace -import timber.log.Timber +import com.getcode.utils.Log import kotlin.reflect.KClass val LocalCodeNavigator: ProvidableCompositionLocal = @@ -240,7 +240,7 @@ class CodeNavigator( * The flow host tears down the flow after receiving this signal. * No-op (with a trace) when called outside a flow — there is no caller to return to. */ - fun navigateBackWithResult(result: android.os.Parcelable) { + fun navigateBackWithResult(result: com.getcode.libs.parcelable.Parcelable) { val scope = flowScope if (scope != null) { scope.exitWithResult(result) @@ -271,7 +271,7 @@ class CodeNavigator( } fun clearToFirst(routeClass: KClass): Boolean { - Timber.d("Clearing backstack to first instance of $routeClass") + Log.d("Clearing backstack to first instance of $routeClass") var routeFound = false Snapshot.withMutableSnapshot { val first = backStack.indexOfFirst { it::class == routeClass } diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/core/FlowScope.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/FlowScope.kt similarity index 96% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/core/FlowScope.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/FlowScope.kt index 4fb06fdfc7..cf8a63f55a 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/core/FlowScope.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/FlowScope.kt @@ -1,6 +1,6 @@ package com.getcode.navigation.core -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable /** * The bounded navigation scope a [CodeNavigator] belongs to when it is a flow's inner navigator. diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/core/NavOptions.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/NavOptions.kt similarity index 89% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/core/NavOptions.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/NavOptions.kt index bf63f4fd67..77307e4aaf 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/core/NavOptions.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/core/NavOptions.kt @@ -1,7 +1,7 @@ package com.getcode.navigation.core import androidx.navigation3.runtime.NavKey -import com.getcode.navigation.BuildConfig +import com.getcode.utils.BuildFlags import kotlin.reflect.KClass /** @@ -14,7 +14,7 @@ import kotlin.reflect.KClass data class NavOptions( val popUpTo: PopUpTo = PopUpTo.None, val navigatingForResult: Boolean = false, - val debugRouting: Boolean = BuildConfig.DEBUG, + val debugRouting: Boolean = BuildFlags.isDebug, ) { sealed interface PopUpTo { data object ClearAll : PopUpTo diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowStep.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/flow/FlowStep.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/flow/FlowStep.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/flow/FlowStep.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResult.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResult.kt similarity index 90% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResult.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResult.kt index ee6401cd63..7568854130 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResult.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResult.kt @@ -1,7 +1,7 @@ package com.getcode.navigation.results -import android.os.Parcelable -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelable +import com.getcode.libs.parcelable.Parcelize /** * NavResultStore value diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultController.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultController.kt similarity index 98% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultController.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultController.kt index d89ea1e356..dcac76fe7f 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultController.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultController.kt @@ -1,6 +1,6 @@ package com.getcode.navigation.results -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.navigation3.runtime.NavKey import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultKey.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultKey.kt similarity index 94% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultKey.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultKey.kt index fe56fbaa2b..34118fc54c 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultKey.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultKey.kt @@ -1,6 +1,6 @@ package com.getcode.navigation.results -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.utils.KClassSerializer import kotlinx.serialization.Serializable import kotlin.reflect.KClass diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultStore.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultStore.kt similarity index 99% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultStore.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultStore.kt index f7c82f0a8f..c668a711ab 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/NavResultStore.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/NavResultStore.kt @@ -1,6 +1,6 @@ package com.getcode.navigation.results -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateMapOf diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/RetVal.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/RetVal.kt similarity index 91% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/results/RetVal.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/RetVal.kt index 79eded14d1..62e2df54fa 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/results/RetVal.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/results/RetVal.kt @@ -1,6 +1,6 @@ package com.getcode.navigation.results -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.navigation3.runtime.NavKey import kotlin.reflect.KClass diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/utils/KClassSerializer.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/utils/KClassSerializer.kt similarity index 100% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/utils/KClassSerializer.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/utils/KClassSerializer.kt diff --git a/ui/navigation/src/main/kotlin/com/getcode/navigation/utils/lifecycle/RepeatOnLifecycle.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/utils/lifecycle/RepeatOnLifecycle.kt similarity index 94% rename from ui/navigation/src/main/kotlin/com/getcode/navigation/utils/lifecycle/RepeatOnLifecycle.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/utils/lifecycle/RepeatOnLifecycle.kt index 3343f78ee3..973409de01 100644 --- a/ui/navigation/src/main/kotlin/com/getcode/navigation/utils/lifecycle/RepeatOnLifecycle.kt +++ b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/utils/lifecycle/RepeatOnLifecycle.kt @@ -2,7 +2,7 @@ package com.getcode.navigation.utils.lifecycle import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope From 6672b93e2c3af795ed55500ead403321f3cc9ba8 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:27:07 -0400 Subject: [PATCH 05/17] build(analytics): move shared:analytics to android+jvm KMP The FlipcashAnalytics interface, the token symbol resolver and the CompositionLocal move to common code. Conversions (toUri and app:core's DeeplinkType) and the Mixpanel delegate stay Android. RecordingAnalytics moves to a :test-fixtures module, since the KMP Android target has no testFixtures; its 10 consumers point at that module. Also moves ui:navigation's BaseViewModel to common code; it uses only lifecycle and coroutines. --- .../features/balance/build.gradle.kts | 2 +- apps/flipcash/features/cash/build.gradle.kts | 2 +- apps/flipcash/features/login/build.gradle.kts | 2 +- .../features/messenger/build.gradle.kts | 2 +- .../features/tipping/build.gradle.kts | 2 +- .../flipcash/features/tokens/build.gradle.kts | 2 +- .../features/user-profile/build.gradle.kts | 2 +- .../shared/analytics/build.gradle.kts | 56 +++++++++++-------- .../flipcash/app/analytics/ConversionsTest.kt | 0 .../app/analytics/MixpanelTrackTest.kt | 0 .../com/flipcash/app/analytics/Conversions.kt | 0 .../app/analytics/inject/AnalyticsModule.kt | 0 .../internal/MixpanelAnalyticsDelegate.kt | 0 .../com/flipcash/app/analytics/Analytics.kt | 0 .../app/analytics/FlipcashAnalytics.kt | 0 .../app/analytics/TokenSymbolResolver.kt | 0 .../analytics/test-fixtures/build.gradle.kts | 15 +++++ .../app/analytics/RecordingAnalytics.kt | 0 apps/flipcash/shared/chat/build.gradle.kts | 2 +- apps/flipcash/shared/session/build.gradle.kts | 2 +- apps/flipcash/shared/tokens/build.gradle.kts | 2 +- .../app/userflags/FieldOverrideTest.kt | 0 .../app/userflags/ResolvedFlagTest.kt | 0 .../app/userflags/ResolvedUserFlagsTest.kt | 0 .../app/userflags/UsernameGateTest.kt | 0 .../res/values/strings.xml | 0 .../com/flipcash/app/userflags/Field.kt | 0 .../com/flipcash/app/userflags/FieldEditor.kt | 0 .../flipcash/app/userflags/FieldOverride.kt | 0 .../app/userflags/ResolvedUserFlags.kt | 0 .../app/userflags/UserFlagsCoordinator.kt | 0 .../flipcash/app/userflags/UsernameGate.kt | 0 settings.gradle.kts | 1 + .../kotlin/com/getcode/view/BaseViewModel.kt | 0 34 files changed, 58 insertions(+), 34 deletions(-) rename apps/flipcash/shared/analytics/src/{test => androidHostTest}/kotlin/com/flipcash/app/analytics/ConversionsTest.kt (100%) rename apps/flipcash/shared/analytics/src/{test => androidHostTest}/kotlin/com/flipcash/app/analytics/MixpanelTrackTest.kt (100%) rename apps/flipcash/shared/analytics/src/{main => androidMain}/kotlin/com/flipcash/app/analytics/Conversions.kt (100%) rename apps/flipcash/shared/analytics/src/{main => androidMain}/kotlin/com/flipcash/app/analytics/inject/AnalyticsModule.kt (100%) rename apps/flipcash/shared/analytics/src/{main => androidMain}/kotlin/com/flipcash/app/analytics/internal/MixpanelAnalyticsDelegate.kt (100%) rename apps/flipcash/shared/analytics/src/{main => commonMain}/kotlin/com/flipcash/app/analytics/Analytics.kt (100%) rename apps/flipcash/shared/analytics/src/{main => commonMain}/kotlin/com/flipcash/app/analytics/FlipcashAnalytics.kt (100%) rename apps/flipcash/shared/analytics/src/{main => commonMain}/kotlin/com/flipcash/app/analytics/TokenSymbolResolver.kt (100%) create mode 100644 apps/flipcash/shared/analytics/test-fixtures/build.gradle.kts rename apps/flipcash/shared/analytics/{src/testFixtures => test-fixtures/src/commonMain}/kotlin/com/flipcash/app/analytics/RecordingAnalytics.kt (100%) rename apps/flipcash/shared/userflags/src/{test => androidHostTest}/kotlin/com/flipcash/app/userflags/FieldOverrideTest.kt (100%) rename apps/flipcash/shared/userflags/src/{test => androidHostTest}/kotlin/com/flipcash/app/userflags/ResolvedFlagTest.kt (100%) rename apps/flipcash/shared/userflags/src/{test => androidHostTest}/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt (100%) rename apps/flipcash/shared/userflags/src/{test => androidHostTest}/kotlin/com/flipcash/app/userflags/UsernameGateTest.kt (100%) rename apps/flipcash/shared/userflags/src/{main => androidMain}/res/values/strings.xml (100%) rename apps/flipcash/shared/userflags/src/{main => commonMain}/kotlin/com/flipcash/app/userflags/Field.kt (100%) rename apps/flipcash/shared/userflags/src/{main => commonMain}/kotlin/com/flipcash/app/userflags/FieldEditor.kt (100%) rename apps/flipcash/shared/userflags/src/{main => commonMain}/kotlin/com/flipcash/app/userflags/FieldOverride.kt (100%) rename apps/flipcash/shared/userflags/src/{main => commonMain}/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt (100%) rename apps/flipcash/shared/userflags/src/{main => commonMain}/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt (100%) rename apps/flipcash/shared/userflags/src/{main => commonMain}/kotlin/com/flipcash/app/userflags/UsernameGate.kt (100%) rename ui/navigation/src/{androidMain => commonMain}/kotlin/com/getcode/view/BaseViewModel.kt (100%) diff --git a/apps/flipcash/features/balance/build.gradle.kts b/apps/flipcash/features/balance/build.gradle.kts index 35b9361647..3c7e428cd6 100644 --- a/apps/flipcash/features/balance/build.gradle.kts +++ b/apps/flipcash/features/balance/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation(project(":apps:flipcash:shared:transaction-history")) implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) implementation(project(":apps:flipcash:shared:chat")) implementation(project(":apps:flipcash:shared:featureflags")) implementation(project(":apps:flipcash:shared:funding")) diff --git a/apps/flipcash/features/cash/build.gradle.kts b/apps/flipcash/features/cash/build.gradle.kts index 748344538f..6faa79e880 100644 --- a/apps/flipcash/features/cash/build.gradle.kts +++ b/apps/flipcash/features/cash/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { implementation(libs.kotlin.stdlib) implementation(project(":apps:flipcash:shared:amount-entry")) implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) implementation(project(":apps:flipcash:shared:featureflags")) implementation(project(":apps:flipcash:shared:session")) implementation(project(":apps:flipcash:shared:tokens")) diff --git a/apps/flipcash/features/login/build.gradle.kts b/apps/flipcash/features/login/build.gradle.kts index fdc0140b53..0fffe64486 100644 --- a/apps/flipcash/features/login/build.gradle.kts +++ b/apps/flipcash/features/login/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation(project(":apps:flipcash:shared:accesskey")) implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) implementation(project(":apps:flipcash:shared:authentication")) implementation(project(":apps:flipcash:shared:contacts")) implementation(project(":apps:flipcash:shared:featureflags")) diff --git a/apps/flipcash/features/messenger/build.gradle.kts b/apps/flipcash/features/messenger/build.gradle.kts index db75e306bb..1db86fa31d 100644 --- a/apps/flipcash/features/messenger/build.gradle.kts +++ b/apps/flipcash/features/messenger/build.gradle.kts @@ -8,7 +8,7 @@ android { dependencies { implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) testImplementation(project(":libs:coroutines:test-fixtures")) implementation(project(":apps:flipcash:shared:blob")) implementation(project(":apps:flipcash:shared:bills")) diff --git a/apps/flipcash/features/tipping/build.gradle.kts b/apps/flipcash/features/tipping/build.gradle.kts index 2c4f4a7910..9af93ef91c 100644 --- a/apps/flipcash/features/tipping/build.gradle.kts +++ b/apps/flipcash/features/tipping/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { testImplementation(kotlin("test")) testImplementation(libs.bundles.unit.testing) testImplementation(project(":ui:resources:test-fixtures")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) testImplementation(libs.robolectric) implementation(project(":apps:flipcash:shared:analytics")) diff --git a/apps/flipcash/features/tokens/build.gradle.kts b/apps/flipcash/features/tokens/build.gradle.kts index e5739b5cb4..de0f8983bc 100644 --- a/apps/flipcash/features/tokens/build.gradle.kts +++ b/apps/flipcash/features/tokens/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { implementation(project(":apps:flipcash:card-expand")) implementation(project(":apps:flipcash:shared:amount-entry")) implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) implementation(project(":apps:flipcash:shared:onramp:coinbase")) implementation(project(":apps:flipcash:shared:onramp:deeplinks")) implementation(project(":apps:flipcash:shared:featureflags")) diff --git a/apps/flipcash/features/user-profile/build.gradle.kts b/apps/flipcash/features/user-profile/build.gradle.kts index e82b55b0a1..1ae121ea60 100644 --- a/apps/flipcash/features/user-profile/build.gradle.kts +++ b/apps/flipcash/features/user-profile/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation(project(":apps:flipcash:shared:amount-entry")) implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) implementation(project(":apps:flipcash:shared:blob")) implementation(project(":apps:flipcash:shared:chat")) implementation(project(":apps:flipcash:shared:common-ui")) diff --git a/apps/flipcash/shared/analytics/build.gradle.kts b/apps/flipcash/shared/analytics/build.gradle.kts index c17b1a59e6..af3b9fdf86 100644 --- a/apps/flipcash/shared/analytics/build.gradle.kts +++ b/apps/flipcash/shared/analytics/build.gradle.kts @@ -1,32 +1,40 @@ plugins { - alias(libs.plugins.flipcash.android.feature) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlin.ksp) } -android { - namespace = "${Gradle.flipcashNamespace}.shared.anaylytics" - testFixtures { - enable = true +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.shared.anaylytics" + } + + sourceSets { + commonMain.dependencies { + api(project(":libs:analytics-events")) + implementation(libs.jetbrains.compose.runtime) + } + androidMain.dependencies { + implementation(project(":apps:flipcash:core")) + implementation(project(":libs:logging")) + implementation(project(":libs:currency-math")) + implementation(project(":libs:encryption:keys")) + implementation(project(":services:flipcash")) + implementation(project(":services:opencode")) + implementation(libs.androidx.core) + implementation(libs.mixpanel) + implementation(libs.bundles.hilt) + } + getByName("androidHostTest").dependencies { + implementation(project(":apps:flipcash:shared:analytics:test-fixtures")) + implementation(kotlin("test")) + implementation(libs.bundles.unit.testing) + implementation(libs.robolectric) + } } } dependencies { - testFixturesImplementation(platform(libs.compose.bom)) - testFixturesImplementation(libs.compose.ui) - - testImplementation(kotlin("test")) - testImplementation(libs.bundles.unit.testing) - testImplementation(libs.robolectric) - - implementation(platform(libs.firebase.bom)) - implementation(libs.firebase.messaging) - implementation(libs.bugsnag) - - api(project(":libs:analytics-events")) - implementation(project(":libs:logging")) - implementation(project(":services:flipcash")) - implementation(project(":services:opencode")) - - implementation(libs.mixpanel) - - implementation(libs.androidx.datastore) + add("kspAndroid", libs.hilt.android.compiler) + add("kspAndroid", libs.hilt.compiler) } diff --git a/apps/flipcash/shared/analytics/src/test/kotlin/com/flipcash/app/analytics/ConversionsTest.kt b/apps/flipcash/shared/analytics/src/androidHostTest/kotlin/com/flipcash/app/analytics/ConversionsTest.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/test/kotlin/com/flipcash/app/analytics/ConversionsTest.kt rename to apps/flipcash/shared/analytics/src/androidHostTest/kotlin/com/flipcash/app/analytics/ConversionsTest.kt diff --git a/apps/flipcash/shared/analytics/src/test/kotlin/com/flipcash/app/analytics/MixpanelTrackTest.kt b/apps/flipcash/shared/analytics/src/androidHostTest/kotlin/com/flipcash/app/analytics/MixpanelTrackTest.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/test/kotlin/com/flipcash/app/analytics/MixpanelTrackTest.kt rename to apps/flipcash/shared/analytics/src/androidHostTest/kotlin/com/flipcash/app/analytics/MixpanelTrackTest.kt diff --git a/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Conversions.kt b/apps/flipcash/shared/analytics/src/androidMain/kotlin/com/flipcash/app/analytics/Conversions.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Conversions.kt rename to apps/flipcash/shared/analytics/src/androidMain/kotlin/com/flipcash/app/analytics/Conversions.kt diff --git a/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/inject/AnalyticsModule.kt b/apps/flipcash/shared/analytics/src/androidMain/kotlin/com/flipcash/app/analytics/inject/AnalyticsModule.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/inject/AnalyticsModule.kt rename to apps/flipcash/shared/analytics/src/androidMain/kotlin/com/flipcash/app/analytics/inject/AnalyticsModule.kt diff --git a/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/internal/MixpanelAnalyticsDelegate.kt b/apps/flipcash/shared/analytics/src/androidMain/kotlin/com/flipcash/app/analytics/internal/MixpanelAnalyticsDelegate.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/internal/MixpanelAnalyticsDelegate.kt rename to apps/flipcash/shared/analytics/src/androidMain/kotlin/com/flipcash/app/analytics/internal/MixpanelAnalyticsDelegate.kt diff --git a/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Analytics.kt b/apps/flipcash/shared/analytics/src/commonMain/kotlin/com/flipcash/app/analytics/Analytics.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Analytics.kt rename to apps/flipcash/shared/analytics/src/commonMain/kotlin/com/flipcash/app/analytics/Analytics.kt diff --git a/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/FlipcashAnalytics.kt b/apps/flipcash/shared/analytics/src/commonMain/kotlin/com/flipcash/app/analytics/FlipcashAnalytics.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/FlipcashAnalytics.kt rename to apps/flipcash/shared/analytics/src/commonMain/kotlin/com/flipcash/app/analytics/FlipcashAnalytics.kt diff --git a/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/TokenSymbolResolver.kt b/apps/flipcash/shared/analytics/src/commonMain/kotlin/com/flipcash/app/analytics/TokenSymbolResolver.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/TokenSymbolResolver.kt rename to apps/flipcash/shared/analytics/src/commonMain/kotlin/com/flipcash/app/analytics/TokenSymbolResolver.kt diff --git a/apps/flipcash/shared/analytics/test-fixtures/build.gradle.kts b/apps/flipcash/shared/analytics/test-fixtures/build.gradle.kts new file mode 100644 index 0000000000..a2e8c1b43e --- /dev/null +++ b/apps/flipcash/shared/analytics/test-fixtures/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(libs.plugins.flipcash.kmp.app.library) +} + +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.shared.analytics.testfixtures" + } + + sourceSets { + commonMain.dependencies { + api(project(":apps:flipcash:shared:analytics")) + } + } +} diff --git a/apps/flipcash/shared/analytics/src/testFixtures/kotlin/com/flipcash/app/analytics/RecordingAnalytics.kt b/apps/flipcash/shared/analytics/test-fixtures/src/commonMain/kotlin/com/flipcash/app/analytics/RecordingAnalytics.kt similarity index 100% rename from apps/flipcash/shared/analytics/src/testFixtures/kotlin/com/flipcash/app/analytics/RecordingAnalytics.kt rename to apps/flipcash/shared/analytics/test-fixtures/src/commonMain/kotlin/com/flipcash/app/analytics/RecordingAnalytics.kt diff --git a/apps/flipcash/shared/chat/build.gradle.kts b/apps/flipcash/shared/chat/build.gradle.kts index a21772a251..2bf81627f5 100644 --- a/apps/flipcash/shared/chat/build.gradle.kts +++ b/apps/flipcash/shared/chat/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { // so this module's public surface names a type from it. api(project(":apps:flipcash:shared:tokens")) implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) implementation(project(":services:flipcash")) testImplementation(project(":services:flipcash:test-fixtures")) implementation(project(":libs:network:connectivity:public")) diff --git a/apps/flipcash/shared/session/build.gradle.kts b/apps/flipcash/shared/session/build.gradle.kts index 9fb357fa99..fe5f4fd58d 100644 --- a/apps/flipcash/shared/session/build.gradle.kts +++ b/apps/flipcash/shared/session/build.gradle.kts @@ -18,7 +18,7 @@ dependencies { implementation(project(":apps:flipcash:shared:contacts")) implementation(project(":apps:flipcash:shared:transaction-history")) implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) implementation(project(":apps:flipcash:shared:appsettings")) implementation(project(":apps:flipcash:shared:google-play-billing")) implementation(project(":apps:flipcash:shared:featureflags")) diff --git a/apps/flipcash/shared/tokens/build.gradle.kts b/apps/flipcash/shared/tokens/build.gradle.kts index e7825e9269..18842e020e 100644 --- a/apps/flipcash/shared/tokens/build.gradle.kts +++ b/apps/flipcash/shared/tokens/build.gradle.kts @@ -25,7 +25,7 @@ dependencies { implementation(project(":apps:flipcash:shared:funding")) implementation(project(":apps:flipcash:shared:transaction-history")) implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(testFixtures(project(":apps:flipcash:shared:analytics"))) + testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) implementation(project(":apps:flipcash:shared:featureflags")) implementation(project(":apps:flipcash:shared:onramp:coinbase")) implementation(project(":apps:flipcash:shared:onramp:deeplinks")) diff --git a/apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/FieldOverrideTest.kt b/apps/flipcash/shared/userflags/src/androidHostTest/kotlin/com/flipcash/app/userflags/FieldOverrideTest.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/FieldOverrideTest.kt rename to apps/flipcash/shared/userflags/src/androidHostTest/kotlin/com/flipcash/app/userflags/FieldOverrideTest.kt diff --git a/apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/ResolvedFlagTest.kt b/apps/flipcash/shared/userflags/src/androidHostTest/kotlin/com/flipcash/app/userflags/ResolvedFlagTest.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/ResolvedFlagTest.kt rename to apps/flipcash/shared/userflags/src/androidHostTest/kotlin/com/flipcash/app/userflags/ResolvedFlagTest.kt diff --git a/apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt b/apps/flipcash/shared/userflags/src/androidHostTest/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt rename to apps/flipcash/shared/userflags/src/androidHostTest/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt diff --git a/apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/UsernameGateTest.kt b/apps/flipcash/shared/userflags/src/androidHostTest/kotlin/com/flipcash/app/userflags/UsernameGateTest.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/UsernameGateTest.kt rename to apps/flipcash/shared/userflags/src/androidHostTest/kotlin/com/flipcash/app/userflags/UsernameGateTest.kt diff --git a/apps/flipcash/shared/userflags/src/main/res/values/strings.xml b/apps/flipcash/shared/userflags/src/androidMain/res/values/strings.xml similarity index 100% rename from apps/flipcash/shared/userflags/src/main/res/values/strings.xml rename to apps/flipcash/shared/userflags/src/androidMain/res/values/strings.xml diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/Field.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/Field.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/Field.kt rename to apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/Field.kt diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/FieldEditor.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/FieldEditor.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/FieldEditor.kt rename to apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/FieldEditor.kt diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/FieldOverride.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/FieldOverride.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/FieldOverride.kt rename to apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/FieldOverride.kt diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt rename to apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt rename to apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UsernameGate.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UsernameGate.kt similarity index 100% rename from apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UsernameGate.kt rename to apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UsernameGate.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 6f3b8c4dee..593daac175 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -124,6 +124,7 @@ include( ":apps:flipcash:shared:amount-entry", ":apps:flipcash:shared:accesskey", ":apps:flipcash:shared:analytics", + ":apps:flipcash:shared:analytics:test-fixtures", ":apps:flipcash:shared:appsettings", ":apps:flipcash:shared:authentication", ":apps:flipcash:shared:activityfeed", diff --git a/ui/navigation/src/androidMain/kotlin/com/getcode/view/BaseViewModel.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/view/BaseViewModel.kt similarity index 100% rename from ui/navigation/src/androidMain/kotlin/com/getcode/view/BaseViewModel.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/view/BaseViewModel.kt From 924470c4aee896a4be61b6564d9c8434f8f4b9bb Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:30:49 -0400 Subject: [PATCH 06/17] build(userflags): move shared:userflags to android+jvm KMP Flag labels sit behind a FlagStrings expect object (Android keeps the R.string ids; desktop has no flag editor). UserFlagsFiles replaces the Context parameter so the DataStore file comes from the platform. --- .../shared/userflags/build.gradle.kts | 56 ++++++++++++------- .../app/userflags/AndroidUserFlagsFiles.kt | 14 +++++ .../app/userflags/FlagStrings.android.kt | 26 +++++++++ .../app/userflags/inject/UserFlagsModule.kt | 15 +++++ .../com/flipcash/app/userflags/Field.kt | 41 +++++++------- .../com/flipcash/app/userflags/FlagStrings.kt | 28 ++++++++++ .../app/userflags/UserFlagsCoordinator.kt | 13 ++--- .../flipcash/app/userflags/UserFlagsFiles.kt | 12 ++++ .../flipcash/app/userflags/FlagStrings.jvm.kt | 24 ++++++++ gradle/libs.versions.toml | 1 + 10 files changed, 182 insertions(+), 48 deletions(-) create mode 100644 apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/AndroidUserFlagsFiles.kt create mode 100644 apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/FlagStrings.android.kt create mode 100644 apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/inject/UserFlagsModule.kt create mode 100644 apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/FlagStrings.kt create mode 100644 apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsFiles.kt create mode 100644 apps/flipcash/shared/userflags/src/jvmMain/kotlin/com/flipcash/app/userflags/FlagStrings.jvm.kt diff --git a/apps/flipcash/shared/userflags/build.gradle.kts b/apps/flipcash/shared/userflags/build.gradle.kts index 38e90ecf0e..365a0bfbf0 100644 --- a/apps/flipcash/shared/userflags/build.gradle.kts +++ b/apps/flipcash/shared/userflags/build.gradle.kts @@ -1,27 +1,45 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) - alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlin.ksp) } -android { - namespace = "${Gradle.flipcashNamespace}.shared.userflags" -} +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.shared.userflags" + androidResources { enable = true } + } -dependencies { - implementation(libs.bundles.hilt) - implementation(libs.androidx.datastore) - implementation(libs.compose.foundation) - implementation(libs.compose.ui.text) - implementation(libs.kotlinx.serialization.json) + sourceSets { + commonMain.dependencies { + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.androidx.annotation) + implementation(libs.androidx.datastore.preferences.core) + implementation(libs.javax.inject) + implementation(libs.kotlinx.serialization.json) - implementation(project(":libs:coroutines")) - implementation(project(":libs:datetime")) - implementation(project(":libs:messaging")) + implementation(project(":libs:coroutines")) + implementation(project(":libs:datetime")) + implementation(project(":libs:messaging")) + implementation(project(":libs:currency-math")) - implementation(project(":services:flipcash")) - implementation(project(":services:opencode")) + implementation(project(":services:flipcash")) + implementation(project(":services:opencode")) + } + androidMain.dependencies { + implementation(libs.bundles.hilt) + implementation(libs.androidx.datastore) + } + getByName("androidHostTest").dependencies { + implementation(kotlin("test")) + implementation(libs.bundles.unit.testing) + implementation(libs.mockito.kotlin) + } + } +} - testImplementation(kotlin("test")) - testImplementation(libs.bundles.unit.testing) - testImplementation(libs.mockito.kotlin) +dependencies { + add("kspAndroid", libs.hilt.android.compiler) + add("kspAndroid", libs.hilt.compiler) } diff --git a/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/AndroidUserFlagsFiles.kt b/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/AndroidUserFlagsFiles.kt new file mode 100644 index 0000000000..af9e6aad23 --- /dev/null +++ b/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/AndroidUserFlagsFiles.kt @@ -0,0 +1,14 @@ +package com.flipcash.app.userflags + +import android.content.Context +import androidx.datastore.preferences.preferencesDataStoreFile +import dagger.hilt.android.qualifiers.ApplicationContext +import java.io.File +import javax.inject.Inject + +internal class AndroidUserFlagsFiles @Inject constructor( + @param:ApplicationContext private val context: Context, +) : UserFlagsFiles { + override val noBackupDir: File get() = context.noBackupFilesDir + override fun dataStoreFile(name: String): File = context.preferencesDataStoreFile(name) +} diff --git a/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/FlagStrings.android.kt b/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/FlagStrings.android.kt new file mode 100644 index 0000000000..bf9407fcfc --- /dev/null +++ b/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/FlagStrings.android.kt @@ -0,0 +1,26 @@ +package com.flipcash.app.userflags + +import com.flipcash.shared.userflags.R + +internal actual object FlagStrings { + actual val hint_flag_billExchangeDataTimeout: Int = R.string.hint_flag_billExchangeDataTimeout + actual val hint_flag_messageDeleteWindow: Int = R.string.hint_flag_messageDeleteWindow + actual val hint_flag_messageEditWindow: Int = R.string.hint_flag_messageEditWindow + actual val hint_flag_minHolderAmount: Int = R.string.hint_flag_minHolderAmount + actual val hint_flag_minimumVersion: Int = R.string.hint_flag_minimumVersion + actual val hint_flag_newCurrencyFeeAmount: Int = R.string.hint_flag_newCurrencyFeeAmount + actual val hint_flag_newCurrencyPurchaseAmount: Int = R.string.hint_flag_newCurrencyPurchaseAmount + actual val hint_flag_withdrawalFeeAmount: Int = R.string.hint_flag_withdrawalFeeAmount + actual val label_flag_billExchangeDataTimeout: Int = R.string.label_flag_billExchangeDataTimeout + actual val label_flag_messageDeleteWindow: Int = R.string.label_flag_messageDeleteWindow + actual val label_flag_messageEditWindow: Int = R.string.label_flag_messageEditWindow + actual val label_flag_minHolderAmount: Int = R.string.label_flag_minHolderAmount + actual val label_flag_minimumVersion: Int = R.string.label_flag_minimumVersion + actual val label_flag_newCurrencyFeeAmount: Int = R.string.label_flag_newCurrencyFeeAmount + actual val label_flag_newCurrencyPurchaseAmount: Int = R.string.label_flag_newCurrencyPurchaseAmount + actual val label_flag_preferredProvider: Int = R.string.label_flag_preferredProvider + actual val label_flag_preferredUsdcLiquidityPool: Int = R.string.label_flag_preferredUsdcLiquidityPool + actual val label_flag_requireCoinbaseEmailVerification: Int = R.string.label_flag_requireCoinbaseEmailVerification + actual val label_flag_supportedOnRampProviders: Int = R.string.label_flag_supportedOnRampProviders + actual val label_flag_withdrawalFeeAmount: Int = R.string.label_flag_withdrawalFeeAmount +} diff --git a/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/inject/UserFlagsModule.kt b/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/inject/UserFlagsModule.kt new file mode 100644 index 0000000000..bbceea8a1e --- /dev/null +++ b/apps/flipcash/shared/userflags/src/androidMain/kotlin/com/flipcash/app/userflags/inject/UserFlagsModule.kt @@ -0,0 +1,15 @@ +package com.flipcash.app.userflags.inject + +import com.flipcash.app.userflags.AndroidUserFlagsFiles +import com.flipcash.app.userflags.UserFlagsFiles +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class UserFlagsModule { + @Binds + abstract fun bindUserFlagsFiles(impl: AndroidUserFlagsFiles): UserFlagsFiles +} diff --git a/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/Field.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/Field.kt index 833a8bf162..6f94b50caa 100644 --- a/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/Field.kt +++ b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/Field.kt @@ -14,7 +14,6 @@ import androidx.datastore.preferences.core.stringSetPreferencesKey import com.flipcash.services.internal.model.thirdparty.OnRampProvider import com.flipcash.services.internal.model.thirdparty.OnRampType import com.flipcash.services.internal.model.thirdparty.UsdcLiquidtyPool -import com.flipcash.shared.userflags.R import com.getcode.opencode.model.financial.Fiat import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -35,7 +34,7 @@ sealed class Field( stringPreferencesKey("override_preferred_provider"), encode = { it?.encode() ?: "None" }, decode = { decodeOnRampProvider(it) }, - label = R.string.label_flag_preferredProvider, + label = FlagStrings.label_flag_preferredProvider, format = { provider -> provider?.displayName() ?: "None" }, editor = FieldEditor.SingleSelect( options = listOf( @@ -57,7 +56,7 @@ sealed class Field( else -> "${providers.size} selected" } }, - label = R.string.label_flag_supportedOnRampProviders, + label = FlagStrings.label_flag_supportedOnRampProviders, editor = FieldEditor.MultiSelect( options = listOf( "Manual Deposit" to OnRampProvider.ManualDeposit, @@ -71,8 +70,8 @@ sealed class Field( intPreferencesKey("override_min_version"), encode = { it ?: 0 }, decode = { it }, - label = R.string.label_flag_minimumVersion, - hint = R.string.hint_flag_minimumVersion, + label = FlagStrings.label_flag_minimumVersion, + hint = FlagStrings.hint_flag_minimumVersion, format = { it.toString() }, editor = FieldEditor.TextInput( keyboard = KeyboardType.Number, @@ -87,8 +86,8 @@ sealed class Field( longPreferencesKey("override_bill_timeout"), encode = { it?.inWholeMilliseconds ?: -1L }, decode = { if (it < 0) null else it.milliseconds }, - label = R.string.label_flag_billExchangeDataTimeout, - hint = R.string.hint_flag_billExchangeDataTimeout, + label = FlagStrings.label_flag_billExchangeDataTimeout, + hint = FlagStrings.hint_flag_billExchangeDataTimeout, format = { it?.let { "${it.inWholeSeconds}s" } ?: "None" }, editFormat = { it?.inWholeSeconds?.toString() ?: "" }, editor = FieldEditor.TextInput( @@ -109,8 +108,8 @@ sealed class Field( longPreferencesKey("override_message_edit_window"), encode = { it?.inWholeMilliseconds ?: -1L }, decode = { if (it < 0) null else it.milliseconds }, - label = R.string.label_flag_messageEditWindow, - hint = R.string.hint_flag_messageEditWindow, + label = FlagStrings.label_flag_messageEditWindow, + hint = FlagStrings.hint_flag_messageEditWindow, format = { it?.let { "${it.inWholeSeconds}s" } ?: "None" }, editFormat = { it?.inWholeSeconds?.toString() ?: "" }, editor = FieldEditor.TextInput( @@ -126,8 +125,8 @@ sealed class Field( longPreferencesKey("override_message_delete_window"), encode = { it?.inWholeMilliseconds ?: -1L }, decode = { if (it < 0) null else it.milliseconds }, - label = R.string.label_flag_messageDeleteWindow, - hint = R.string.hint_flag_messageDeleteWindow, + label = FlagStrings.label_flag_messageDeleteWindow, + hint = FlagStrings.hint_flag_messageDeleteWindow, format = { it?.let { "${it.inWholeSeconds}s" } ?: "None" }, editFormat = { it?.inWholeSeconds?.toString() ?: "" }, editor = FieldEditor.TextInput( @@ -143,8 +142,8 @@ sealed class Field( longPreferencesKey("override_new_currency_amount"), encode = { it.quarks }, decode = { Fiat(quarks = it) }, - label = R.string.label_flag_newCurrencyPurchaseAmount, - hint = R.string.hint_flag_newCurrencyPurchaseAmount, + label = FlagStrings.label_flag_newCurrencyPurchaseAmount, + hint = FlagStrings.hint_flag_newCurrencyPurchaseAmount, format = { it.formatted(rule = Fiat.FormattingRule.Truncated) }, editFormat = { it.formatted(showPrefix = false, rule = Fiat.FormattingRule.Truncated) }, editor = FieldEditor.TextInput( @@ -162,8 +161,8 @@ sealed class Field( longPreferencesKey("override_new_fee_amount"), encode = { it.quarks }, decode = { Fiat(quarks = it) }, - label = R.string.label_flag_newCurrencyFeeAmount, - hint = R.string.hint_flag_newCurrencyFeeAmount, + label = FlagStrings.label_flag_newCurrencyFeeAmount, + hint = FlagStrings.hint_flag_newCurrencyFeeAmount, format = { it.formatted(rule = Fiat.FormattingRule.Truncated) }, editFormat = { it.formatted(showPrefix = false, rule = Fiat.FormattingRule.Truncated) }, editor = FieldEditor.TextInput( @@ -181,8 +180,8 @@ sealed class Field( longPreferencesKey("override_withdrawal_fee_amount"), encode = { it.quarks }, decode = { Fiat(quarks = it) }, - label = R.string.label_flag_withdrawalFeeAmount, - hint = R.string.hint_flag_withdrawalFeeAmount, + label = FlagStrings.label_flag_withdrawalFeeAmount, + hint = FlagStrings.hint_flag_withdrawalFeeAmount, format = { it.formatted(rule = Fiat.FormattingRule.Truncated) }, editFormat = { it.formatted(showPrefix = false, rule = Fiat.FormattingRule.Truncated) }, editor = FieldEditor.TextInput( @@ -200,7 +199,7 @@ sealed class Field( stringPreferencesKey("override_preferred_liquidity_pool"), encode = { it.name }, decode = { runCatching { UsdcLiquidtyPool.valueOf(it) }.getOrNull() }, - label = R.string.label_flag_preferredUsdcLiquidityPool, + label = FlagStrings.label_flag_preferredUsdcLiquidityPool, format = { provider -> provider.displayName() }, editor = FieldEditor.SingleSelect( options = listOf( @@ -214,7 +213,7 @@ sealed class Field( booleanPreferencesKey("override_require_coinbase_email_verification"), encode = { it }, decode = { it }, - label = R.string.label_flag_requireCoinbaseEmailVerification, + label = FlagStrings.label_flag_requireCoinbaseEmailVerification, format = { if (it) "Yes" else "No" }, editor = FieldEditor.SingleSelect( options = listOf("Yes" to true, "No" to false) @@ -225,8 +224,8 @@ sealed class Field( longPreferencesKey("override_min_holder_amount"), encode = { it.quarks }, decode = { Fiat(quarks = it) }, - label = R.string.label_flag_minHolderAmount, - hint = R.string.hint_flag_minHolderAmount, + label = FlagStrings.label_flag_minHolderAmount, + hint = FlagStrings.hint_flag_minHolderAmount, format = { it.formatted(rule = Fiat.FormattingRule.Truncated) }, editFormat = { it.formatted(showPrefix = false, rule = Fiat.FormattingRule.Truncated) }, editor = FieldEditor.TextInput( diff --git a/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/FlagStrings.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/FlagStrings.kt new file mode 100644 index 0000000000..f7923bed81 --- /dev/null +++ b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/FlagStrings.kt @@ -0,0 +1,28 @@ +package com.flipcash.app.userflags + +/** + * String resource ids for the debug flag editor's labels and hints. Android reads them from + * this module's `R`; desktop has no flag editor, so its ids are 0. + */ +internal expect object FlagStrings { + val hint_flag_billExchangeDataTimeout: Int + val hint_flag_messageDeleteWindow: Int + val hint_flag_messageEditWindow: Int + val hint_flag_minHolderAmount: Int + val hint_flag_minimumVersion: Int + val hint_flag_newCurrencyFeeAmount: Int + val hint_flag_newCurrencyPurchaseAmount: Int + val hint_flag_withdrawalFeeAmount: Int + val label_flag_billExchangeDataTimeout: Int + val label_flag_messageDeleteWindow: Int + val label_flag_messageEditWindow: Int + val label_flag_minHolderAmount: Int + val label_flag_minimumVersion: Int + val label_flag_newCurrencyFeeAmount: Int + val label_flag_newCurrencyPurchaseAmount: Int + val label_flag_preferredProvider: Int + val label_flag_preferredUsdcLiquidityPool: Int + val label_flag_requireCoinbaseEmailVerification: Int + val label_flag_supportedOnRampProviders: Int + val label_flag_withdrawalFeeAmount: Int +} diff --git a/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt index 04a5c91e03..fb9bb49c9c 100644 --- a/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt +++ b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt @@ -1,12 +1,10 @@ package com.flipcash.app.userflags -import android.content.Context import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.preferencesDataStoreFile import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.services.internal.model.thirdparty.OnRampProvider import com.flipcash.services.internal.model.thirdparty.OnRampType @@ -16,7 +14,6 @@ import com.flipcash.services.models.UserFlags import com.flipcash.services.user.UserManager import com.getcode.opencode.model.financial.CurrencyCode import com.getcode.opencode.model.financial.Fiat -import dagger.hilt.android.qualifiers.ApplicationContext import java.io.File import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -38,7 +35,7 @@ import kotlin.time.Duration.Companion.milliseconds @Singleton class UserFlagsCoordinator @Inject constructor( - @param:ApplicationContext private val context: Context, + private val files: UserFlagsFiles, private val userManager: UserManager, dispatchers: DispatcherProvider, ) { @@ -80,9 +77,9 @@ class UserFlagsCoordinator @Inject constructor( init { // Delete the backing file before DataStore reads it to avoid a race // where stale overrides restored from backup are briefly visible. - val marker = File(context.noBackupFilesDir, "user-flag-overrides-initialized") + val marker = File(files.noBackupDir, "user-flag-overrides-initialized") if (!marker.exists()) { - context.preferencesDataStoreFile("user-flag-overrides").delete() + files.dataStoreFile("user-flag-overrides").delete() marker.createNewFile() } @@ -98,7 +95,7 @@ class UserFlagsCoordinator @Inject constructor( private val dataStore = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = scope, - produceFile = { context.preferencesDataStoreFile("user-flag-overrides") } + produceFile = { files.dataStoreFile("user-flag-overrides") } ) // Parse DataStore prefs → Overrides @@ -145,7 +142,7 @@ class UserFlagsCoordinator @Inject constructor( private val cacheDataStore = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = scope, - produceFile = { context.preferencesDataStoreFile("cached-user-flags") } + produceFile = { files.dataStoreFile("cached-user-flags") } ) suspend fun restoreFlags() { diff --git a/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsFiles.kt b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsFiles.kt new file mode 100644 index 0000000000..b61b3e6949 --- /dev/null +++ b/apps/flipcash/shared/userflags/src/commonMain/kotlin/com/flipcash/app/userflags/UserFlagsFiles.kt @@ -0,0 +1,12 @@ +package com.flipcash.app.userflags + +import java.io.File + +/** Where [UserFlagsCoordinator] keeps its DataStore files and its first-run marker. */ +interface UserFlagsFiles { + /** Directory excluded from backup, for the first-run marker. */ + val noBackupDir: File + + /** The file backing the preferences DataStore called [name]. */ + fun dataStoreFile(name: String): File +} diff --git a/apps/flipcash/shared/userflags/src/jvmMain/kotlin/com/flipcash/app/userflags/FlagStrings.jvm.kt b/apps/flipcash/shared/userflags/src/jvmMain/kotlin/com/flipcash/app/userflags/FlagStrings.jvm.kt new file mode 100644 index 0000000000..5e9c5fbd6d --- /dev/null +++ b/apps/flipcash/shared/userflags/src/jvmMain/kotlin/com/flipcash/app/userflags/FlagStrings.jvm.kt @@ -0,0 +1,24 @@ +package com.flipcash.app.userflags + +internal actual object FlagStrings { + actual val hint_flag_billExchangeDataTimeout: Int = 0 + actual val hint_flag_messageDeleteWindow: Int = 0 + actual val hint_flag_messageEditWindow: Int = 0 + actual val hint_flag_minHolderAmount: Int = 0 + actual val hint_flag_minimumVersion: Int = 0 + actual val hint_flag_newCurrencyFeeAmount: Int = 0 + actual val hint_flag_newCurrencyPurchaseAmount: Int = 0 + actual val hint_flag_withdrawalFeeAmount: Int = 0 + actual val label_flag_billExchangeDataTimeout: Int = 0 + actual val label_flag_messageDeleteWindow: Int = 0 + actual val label_flag_messageEditWindow: Int = 0 + actual val label_flag_minHolderAmount: Int = 0 + actual val label_flag_minimumVersion: Int = 0 + actual val label_flag_newCurrencyFeeAmount: Int = 0 + actual val label_flag_newCurrencyPurchaseAmount: Int = 0 + actual val label_flag_preferredProvider: Int = 0 + actual val label_flag_preferredUsdcLiquidityPool: Int = 0 + actual val label_flag_requireCoinbaseEmailVerification: Int = 0 + actual val label_flag_supportedOnRampProviders: Int = 0 + actual val label_flag_withdrawalFeeAmount: Int = 0 +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a21628234f..f9356a8896 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -167,6 +167,7 @@ androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.r androidx-work = { module = "androidx.work:work-runtime", version.ref = "androidx-work" } androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidx-work" } androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" } +androidx-datastore-preferences-core = { module = "androidx.datastore:datastore-preferences-core", version.ref = "androidx-datastore" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "androidx-webkit" } sqlcipher = { module = "net.zetetic:android-database-sqlcipher", version.ref = "sqlcipher" } From 90afa5e6438c72dd8ad9dd670b0b478bf5cbbd6a Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:37:16 -0400 Subject: [PATCH 07/17] build(core): lift AppRoute and flow step types to common AppRoute, its step and result types, DeviceContact and TipCardOwner move to app:core's commonMain with the shared Parcelable aliases. The three files that use Uri or R (CurrencyCreatorDraft, TokenDisplayName, DeeplinkType) stay in androidMain. FlowRoute moves to ui:navigation's commonMain. ui:resources adds stringRes/pluralStringRes for composables that hold an Int resource ID, and lets other modules register their own Compose resources with ComposeResourceIds to get an Int ID on desktop. --- apps/flipcash/core/build.gradle.kts | 6 +++ .../kotlin/com/flipcash/app/core/AppRoute.kt | 4 +- .../flipcash/app/core/chat/ChatIdentifier.kt | 4 +- .../flipcash/app/core/chat/ChatParticipant.kt | 4 +- .../com/flipcash/app/core/chat/ChatStep.kt | 4 +- .../flipcash/app/core/chat/DmDestination.kt | 0 .../flipcash/app/core/chat/NewGroupStep.kt | 4 +- .../flipcash/app/core/chat/ProfileAddress.kt | 4 +- .../flipcash/app/core/chat/ReportSubject.kt | 4 +- .../app/core/contacts/DeviceContact.kt | 4 +- .../app/core/deposit/DepositResult.kt | 4 +- .../flipcash/app/core/deposit/DepositStep.kt | 4 +- .../app/core/extensions/CodeNavigator.kt | 0 .../app/core/navigation/DeeplinkAction.kt | 0 .../flipcash/app/core/navigation/HomeRoute.kt | 0 .../app/core/navigation/LocalTabBarPadding.kt | 0 .../app/core/navigation/NavBarButton.kt | 0 .../app/core/navigation/NavBarRoutes.kt | 0 .../app/core/navigation/SteppedFlowRoute.kt | 2 +- .../app/core/navigation/TabBarVisibility.kt | 0 .../app/core/onboarding/OnboardingStep.kt | 4 +- .../flipcash/app/core/reporting/ReportStep.kt | 4 +- .../flipcash/app/core/tipping/TipCardOwner.kt | 0 .../app/core/tokens/CurrencyCreatorResult.kt | 4 +- .../app/core/tokens/CurrencyCreatorStep.kt | 4 +- .../flipcash/app/core/tokens/SwapResult.kt | 4 +- .../com/flipcash/app/core/tokens/SwapStep.kt | 4 +- .../app/core/tokens/TokenInfoEntry.kt | 4 +- .../flipcash/app/core/tokens/TokenPurpose.kt | 4 +- .../app/core/tokens/TokenSwapPurpose.kt | 4 +- .../core/userprofile/UpdateProfileResult.kt | 4 +- .../app/core/userprofile/UpdateProfileStep.kt | 4 +- .../core/verification/VerificationResult.kt | 4 +- .../app/core/verification/VerificationStep.kt | 4 +- .../app/core/withdrawal/WithdrawalResult.kt | 4 +- .../app/core/withdrawal/WithdrawalStep.kt | 4 +- .../com/getcode/navigation/flow/FlowRoute.kt | 0 .../util/resources/StringRes.android.kt | 15 +++++++ .../com/getcode/util/resources/StringRes.kt | 18 ++++++++ .../util/resources/ComposeResourceIds.kt | 45 +++++++++++++++++-- .../getcode/util/resources/StringRes.jvm.kt | 16 +++++++ 41 files changed, 147 insertions(+), 55 deletions(-) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/AppRoute.kt (99%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/chat/ChatIdentifier.kt (93%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/chat/ChatParticipant.kt (95%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/chat/ChatStep.kt (99%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/chat/DmDestination.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/chat/NewGroupStep.kt (95%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/chat/ProfileAddress.kt (94%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/chat/ReportSubject.kt (93%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/contacts/DeviceContact.kt (89%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/deposit/DepositResult.kt (76%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/deposit/DepositStep.kt (87%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/extensions/CodeNavigator.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/navigation/DeeplinkAction.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/navigation/HomeRoute.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/navigation/LocalTabBarPadding.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/navigation/NavBarRoutes.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/navigation/SteppedFlowRoute.kt (93%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/navigation/TabBarVisibility.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/onboarding/OnboardingStep.kt (94%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/reporting/ReportStep.kt (94%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/tipping/TipCardOwner.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorResult.kt (83%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorStep.kt (94%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/tokens/SwapResult.kt (86%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/tokens/SwapStep.kt (95%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/tokens/TokenInfoEntry.kt (97%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/tokens/TokenPurpose.kt (93%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/tokens/TokenSwapPurpose.kt (94%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/userprofile/UpdateProfileResult.kt (80%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/userprofile/UpdateProfileStep.kt (94%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/verification/VerificationResult.kt (80%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/verification/VerificationStep.kt (91%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/withdrawal/WithdrawalResult.kt (76%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/withdrawal/WithdrawalStep.kt (90%) rename ui/navigation/src/{androidMain => commonMain}/kotlin/com/getcode/navigation/flow/FlowRoute.kt (100%) create mode 100644 ui/resources/src/androidMain/kotlin/com/getcode/util/resources/StringRes.android.kt create mode 100644 ui/resources/src/commonMain/kotlin/com/getcode/util/resources/StringRes.kt create mode 100644 ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/StringRes.jvm.kt diff --git a/apps/flipcash/core/build.gradle.kts b/apps/flipcash/core/build.gradle.kts index 0967c1843a..3deb1a4c65 100644 --- a/apps/flipcash/core/build.gradle.kts +++ b/apps/flipcash/core/build.gradle.kts @@ -14,9 +14,15 @@ kotlin { sourceSets { commonMain.dependencies { + implementation(project(":libs:parcelable")) + api(project(":ui:navigation")) + api(project(":ui:core")) + api(libs.navigation3.runtime) implementation(libs.kotlinx.serialization.core) implementation(libs.kotlinx.serialization.json) implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + api(project(":libs:reporting")) api(project(":services:flipcash")) api(project(":services:opencode")) diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/AppRoute.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/AppRoute.kt similarity index 99% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/AppRoute.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/AppRoute.kt index 03254b422e..52b268b1d9 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/AppRoute.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/AppRoute.kt @@ -1,6 +1,6 @@ package com.flipcash.app.core -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.navigation3.runtime.NavKey import com.flipcash.app.core.chat.ChatIdentifier import com.flipcash.app.core.chat.ChatStep @@ -36,7 +36,7 @@ import com.getcode.opencode.model.core.ID import com.getcode.opencode.model.financial.Fiat import com.getcode.solana.keys.Mint import com.getcode.ui.core.RestrictionType -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable import kotlin.reflect.KClass diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatIdentifier.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatIdentifier.kt similarity index 93% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatIdentifier.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatIdentifier.kt index d6dfd7347d..fac5c262f2 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatIdentifier.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatIdentifier.kt @@ -1,12 +1,12 @@ package com.flipcash.app.core.chat -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.flipcash.app.core.contacts.DeviceContact import com.flipcash.services.models.UserProfile import com.flipcash.services.models.chat.ChatId import com.getcode.opencode.model.core.ID import com.getcode.utils.hexEncodedString -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatParticipant.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatParticipant.kt similarity index 95% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatParticipant.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatParticipant.kt index 930ebb662f..3ada98b8a7 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatParticipant.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatParticipant.kt @@ -1,12 +1,12 @@ package com.flipcash.app.core.chat -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.flipcash.app.core.contacts.DeviceContact import com.flipcash.services.models.UserProfile import com.flipcash.services.models.handle import com.flipcash.services.models.nameOrHandle import com.getcode.opencode.model.core.ID -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize /** * The counterparty a DM header and info card renders. diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatStep.kt similarity index 99% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatStep.kt index 4cdc7d4694..40fc67cfff 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ChatStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ChatStep.kt @@ -1,13 +1,13 @@ package com.flipcash.app.core.chat -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.flipcash.services.models.chat.ChatId import com.getcode.navigation.HalfSheet import com.getcode.navigation.Sheet import com.getcode.navigation.WrapContentSheet import com.getcode.navigation.flow.FlowStep import com.getcode.navigation.results.NavigationRetVal -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Parcelize diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/DmDestination.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/DmDestination.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/DmDestination.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/DmDestination.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/NewGroupStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/NewGroupStep.kt similarity index 95% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/NewGroupStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/NewGroupStep.kt index 99cf40f50b..121789a5fb 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/NewGroupStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/NewGroupStep.kt @@ -1,9 +1,9 @@ package com.flipcash.app.core.chat -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.Sheet import com.getcode.navigation.flow.FlowStep -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ProfileAddress.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ProfileAddress.kt similarity index 94% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ProfileAddress.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ProfileAddress.kt index 810aac7056..9b87c95d4c 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ProfileAddress.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ProfileAddress.kt @@ -1,9 +1,9 @@ package com.flipcash.app.core.chat -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.flipcash.app.core.tipping.TipCardOwner import com.getcode.opencode.model.core.ID -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ReportSubject.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ReportSubject.kt similarity index 93% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ReportSubject.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ReportSubject.kt index 3a11b14a5d..434f8e5f97 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/chat/ReportSubject.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/ReportSubject.kt @@ -1,9 +1,9 @@ package com.flipcash.app.core.chat -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.flipcash.services.models.chat.ChatId import com.getcode.opencode.model.core.ID -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/contacts/DeviceContact.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/contacts/DeviceContact.kt similarity index 89% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/contacts/DeviceContact.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/contacts/DeviceContact.kt index fa7f12aafa..2cfcbe5268 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/contacts/DeviceContact.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/contacts/DeviceContact.kt @@ -1,7 +1,7 @@ package com.flipcash.app.core.contacts -import android.os.Parcelable -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelable +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/deposit/DepositResult.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/deposit/DepositResult.kt similarity index 76% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/deposit/DepositResult.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/deposit/DepositResult.kt index 87010430d3..e9b5764cf6 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/deposit/DepositResult.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/deposit/DepositResult.kt @@ -1,7 +1,7 @@ package com.flipcash.app.core.deposit -import android.os.Parcelable -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelable +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/deposit/DepositStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/deposit/DepositStep.kt similarity index 87% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/deposit/DepositStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/deposit/DepositStep.kt index 8cb18871f2..1e5bfb56d7 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/deposit/DepositStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/deposit/DepositStep.kt @@ -1,9 +1,9 @@ package com.flipcash.app.core.deposit -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.flow.FlowStep import com.getcode.solana.keys.Mint -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/extensions/CodeNavigator.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/extensions/CodeNavigator.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/extensions/CodeNavigator.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/extensions/CodeNavigator.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/DeeplinkAction.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/DeeplinkAction.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/DeeplinkAction.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/DeeplinkAction.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/HomeRoute.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/HomeRoute.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/HomeRoute.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/HomeRoute.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/LocalTabBarPadding.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/LocalTabBarPadding.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/LocalTabBarPadding.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/LocalTabBarPadding.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/NavBarButton.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/NavBarRoutes.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/NavBarRoutes.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/NavBarRoutes.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/NavBarRoutes.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/SteppedFlowRoute.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/SteppedFlowRoute.kt similarity index 93% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/SteppedFlowRoute.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/SteppedFlowRoute.kt index 9e08de1e33..df3248c1cb 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/SteppedFlowRoute.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/SteppedFlowRoute.kt @@ -1,6 +1,6 @@ package com.flipcash.app.core.navigation -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.flow.FlowRouteWithResult import com.getcode.navigation.flow.FlowStep import kotlin.reflect.KClass diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/TabBarVisibility.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/TabBarVisibility.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/navigation/TabBarVisibility.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/navigation/TabBarVisibility.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/onboarding/OnboardingStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/onboarding/OnboardingStep.kt similarity index 94% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/onboarding/OnboardingStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/onboarding/OnboardingStep.kt index 318fcaf3f0..6b1a657c25 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/onboarding/OnboardingStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/onboarding/OnboardingStep.kt @@ -1,9 +1,9 @@ package com.flipcash.app.core.onboarding -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.NonDismissableRoute import com.getcode.navigation.flow.FlowStep -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/reporting/ReportStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/reporting/ReportStep.kt similarity index 94% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/reporting/ReportStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/reporting/ReportStep.kt index 3e09d6d291..af5b1ab24f 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/reporting/ReportStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/reporting/ReportStep.kt @@ -1,10 +1,10 @@ package com.flipcash.app.core.reporting -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.flipcash.reporting.ReportReason import com.getcode.navigation.NonDraggableRoute import com.getcode.navigation.flow.FlowStep -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tipping/TipCardOwner.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tipping/TipCardOwner.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tipping/TipCardOwner.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tipping/TipCardOwner.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorResult.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorResult.kt similarity index 83% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorResult.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorResult.kt index 6c52cc5465..4a6593d935 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorResult.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorResult.kt @@ -1,7 +1,7 @@ package com.flipcash.app.core.tokens -import android.os.Parcelable -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelable +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorStep.kt similarity index 94% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorStep.kt index eaa999df84..54b83cc212 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/CurrencyCreatorStep.kt @@ -1,12 +1,12 @@ package com.flipcash.app.core.tokens -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.NonDismissableRoute import com.getcode.navigation.NonDraggableRoute import com.getcode.navigation.flow.FlowStep import com.getcode.opencode.model.financial.Fiat import com.getcode.opencode.model.ui.TokenBillCustomizations -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/SwapResult.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/SwapResult.kt similarity index 86% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/SwapResult.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/SwapResult.kt index bd1d82c74f..daedc148d2 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/SwapResult.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/SwapResult.kt @@ -1,8 +1,8 @@ package com.flipcash.app.core.tokens -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.opencode.model.financial.Fiat -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/SwapStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/SwapStep.kt similarity index 95% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/SwapStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/SwapStep.kt index bc29cc68c1..4bfd249cd5 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/SwapStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/SwapStep.kt @@ -1,6 +1,6 @@ package com.flipcash.app.core.tokens -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.HalfSheet import com.getcode.navigation.NonDismissableRoute import com.getcode.navigation.NonDraggableRoute @@ -9,7 +9,7 @@ import com.getcode.navigation.WrapContentSheet import com.getcode.navigation.flow.FlowStep import com.getcode.opencode.internal.solana.model.SwapId import com.getcode.opencode.model.financial.Fiat -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenInfoEntry.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenInfoEntry.kt similarity index 97% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenInfoEntry.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenInfoEntry.kt index 3fcc0b9cde..8cda25dec3 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenInfoEntry.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenInfoEntry.kt @@ -1,8 +1,8 @@ package com.flipcash.app.core.tokens -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.solana.keys.Mint -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenPurpose.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenPurpose.kt similarity index 93% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenPurpose.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenPurpose.kt index ad73d2af65..ba0d49364b 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenPurpose.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenPurpose.kt @@ -1,9 +1,9 @@ package com.flipcash.app.core.tokens -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.opencode.model.financial.Fiat import com.getcode.solana.keys.Mint -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenSwapPurpose.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenSwapPurpose.kt similarity index 94% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenSwapPurpose.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenSwapPurpose.kt index d45cae9721..46241a1aa2 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/tokens/TokenSwapPurpose.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/tokens/TokenSwapPurpose.kt @@ -1,8 +1,8 @@ package com.flipcash.app.core.tokens -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.solana.keys.Mint -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileResult.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileResult.kt similarity index 80% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileResult.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileResult.kt index ecbd0ce6c7..86c2968f8a 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileResult.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileResult.kt @@ -1,7 +1,7 @@ package com.flipcash.app.core.userprofile -import android.os.Parcelable -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelable +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileStep.kt similarity index 94% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileStep.kt index f355f0592e..850d354f29 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/userprofile/UpdateProfileStep.kt @@ -1,9 +1,9 @@ package com.flipcash.app.core.userprofile -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.flipcash.app.core.DisplayNameSource import com.getcode.navigation.flow.FlowStep -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/verification/VerificationResult.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/verification/VerificationResult.kt similarity index 80% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/verification/VerificationResult.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/verification/VerificationResult.kt index dbe23df81c..7b921f846f 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/verification/VerificationResult.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/verification/VerificationResult.kt @@ -1,7 +1,7 @@ package com.flipcash.app.core.verification -import android.os.Parcelable -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelable +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/verification/VerificationStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/verification/VerificationStep.kt similarity index 91% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/verification/VerificationStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/verification/VerificationStep.kt index 76e85dd9e0..a3053071ea 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/verification/VerificationStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/verification/VerificationStep.kt @@ -1,8 +1,8 @@ package com.flipcash.app.core.verification -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.flow.FlowStep -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalResult.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalResult.kt similarity index 76% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalResult.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalResult.kt index 951379a37f..f7ca5b6492 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalResult.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalResult.kt @@ -1,7 +1,7 @@ package com.flipcash.app.core.withdrawal -import android.os.Parcelable -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelable +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable @Serializable diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalStep.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalStep.kt similarity index 90% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalStep.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalStep.kt index 2500725704..c9fb525778 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalStep.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/withdrawal/WithdrawalStep.kt @@ -1,10 +1,10 @@ package com.flipcash.app.core.withdrawal -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import com.getcode.navigation.flow.FlowStep import com.getcode.opencode.internal.solana.model.SwapId import com.getcode.solana.keys.Mint -import kotlinx.parcelize.Parcelize +import com.getcode.libs.parcelable.Parcelize import kotlinx.serialization.Serializable /** diff --git a/ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowRoute.kt b/ui/navigation/src/commonMain/kotlin/com/getcode/navigation/flow/FlowRoute.kt similarity index 100% rename from ui/navigation/src/androidMain/kotlin/com/getcode/navigation/flow/FlowRoute.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/navigation/flow/FlowRoute.kt diff --git a/ui/resources/src/androidMain/kotlin/com/getcode/util/resources/StringRes.android.kt b/ui/resources/src/androidMain/kotlin/com/getcode/util/resources/StringRes.android.kt new file mode 100644 index 0000000000..5819f8d2f9 --- /dev/null +++ b/ui/resources/src/androidMain/kotlin/com/getcode/util/resources/StringRes.android.kt @@ -0,0 +1,15 @@ +package com.getcode.util.resources + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource + +@Composable +actual fun stringRes(id: Int): String = stringResource(id) + +@Composable +actual fun stringRes(id: Int, vararg formatArgs: Any): String = stringResource(id, *formatArgs) + +@Composable +actual fun pluralStringRes(id: Int, count: Int, vararg formatArgs: Any): String = + pluralStringResource(id, count, *formatArgs) diff --git a/ui/resources/src/commonMain/kotlin/com/getcode/util/resources/StringRes.kt b/ui/resources/src/commonMain/kotlin/com/getcode/util/resources/StringRes.kt new file mode 100644 index 0000000000..5bb17bec3e --- /dev/null +++ b/ui/resources/src/commonMain/kotlin/com/getcode/util/resources/StringRes.kt @@ -0,0 +1,18 @@ +package com.getcode.util.resources + +import androidx.annotation.PluralsRes +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable + +/** + * Reads the string behind a resource ID in composition. On Android these are `R.string` values; + * on desktop they come from [ResourceHelper.getIdentifier] or a module's string table. + */ +@Composable +expect fun stringRes(@StringRes id: Int): String + +@Composable +expect fun stringRes(@StringRes id: Int, vararg formatArgs: Any): String + +@Composable +expect fun pluralStringRes(@PluralsRes id: Int, count: Int, vararg formatArgs: Any): String diff --git a/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt index 898ed8033f..9a15779c2c 100644 --- a/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt +++ b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt @@ -6,23 +6,60 @@ import org.jetbrains.compose.resources.StringResource /** * Stable `Int` IDs for the generated Compose resources, so desktop can serve the `Int`-keyed - * [ResourceHelper] API. Each resource type gets its own range, ordered by resource name. + * [ResourceHelper] API. + * + * `ui:resources`' own resources get fixed ranges ordered by name. Other modules keep their own + * `Res` class and register each resource they hand out, getting an ID from a separate range. */ object ComposeResourceIds { private const val DRAWABLE_BASE = 0x7f010000 private const val STRING_BASE = 0x7f020000 private const val PLURAL_BASE = 0x7f030000 + private const val REGISTERED_DRAWABLE_BASE = 0x7f040000 + private const val REGISTERED_STRING_BASE = 0x7f050000 + private const val REGISTERED_PLURAL_BASE = 0x7f060000 + private const val RANGE = 0x10000 private val drawables: List> by lazy { Res.allDrawableResources.toSortedList() } private val strings: List> by lazy { Res.allStringResources.toSortedList() } private val plurals: List> by lazy { Res.allPluralStringResources.toSortedList() } + private val registeredDrawables = Registry(REGISTERED_DRAWABLE_BASE) + private val registeredStrings = Registry(REGISTERED_STRING_BASE) + private val registeredPlurals = Registry(REGISTERED_PLURAL_BASE) + fun drawableId(name: String): Int? = drawables.indexOfName(name)?.plus(DRAWABLE_BASE) fun stringId(name: String): Int? = strings.indexOfName(name)?.plus(STRING_BASE) - fun drawable(id: Int): DrawableResource = drawables.at(id - DRAWABLE_BASE, id) - fun string(id: Int): StringResource = strings.at(id - STRING_BASE, id) - fun plural(id: Int): PluralStringResource = plurals.at(id - PLURAL_BASE, id) + fun register(resource: DrawableResource): Int = registeredDrawables.idOf(resource) + fun register(resource: StringResource): Int = registeredStrings.idOf(resource) + fun register(resource: PluralStringResource): Int = registeredPlurals.idOf(resource) + + fun drawable(id: Int): DrawableResource = + if (id.inRange(REGISTERED_DRAWABLE_BASE)) registeredDrawables[id] else drawables.at(id - DRAWABLE_BASE, id) + + fun string(id: Int): StringResource = + if (id.inRange(REGISTERED_STRING_BASE)) registeredStrings[id] else strings.at(id - STRING_BASE, id) + + fun plural(id: Int): PluralStringResource = + if (id.inRange(REGISTERED_PLURAL_BASE)) registeredPlurals[id] else plurals.at(id - PLURAL_BASE, id) + + private fun Int.inRange(base: Int) = this >= base && this < base + RANGE + + private class Registry(private val base: Int) { + private val ids = HashMap() + private val resources = ArrayList() + + @Synchronized + fun idOf(resource: T): Int = ids.getOrPut(resource) { + resources.add(resource) + base + resources.size - 1 + } + + @Synchronized + operator fun get(id: Int): T = resources.getOrNull(id - base) + ?: throw IllegalArgumentException("No Compose resource for ID 0x${id.toString(16)}") + } private fun Map.toSortedList() = entries.sortedBy { it.key }.map { it.key to it.value } diff --git a/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/StringRes.jvm.kt b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/StringRes.jvm.kt new file mode 100644 index 0000000000..6e9a8f1313 --- /dev/null +++ b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/StringRes.jvm.kt @@ -0,0 +1,16 @@ +package com.getcode.util.resources + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.resources.pluralStringResource +import org.jetbrains.compose.resources.stringResource + +@Composable +actual fun stringRes(id: Int): String = stringResource(ComposeResourceIds.string(id)) + +@Composable +actual fun stringRes(id: Int, vararg formatArgs: Any): String = + stringResource(ComposeResourceIds.string(id), *formatArgs) + +@Composable +actual fun pluralStringRes(id: Int, count: Int, vararg formatArgs: Any): String = + pluralStringResource(ComposeResourceIds.plural(id), count, *formatArgs) From 57f1d91cdc085e17d80b8898a9f508ff9b886737 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:46:50 -0400 Subject: [PATCH 08/17] build(core-ui): move core-ui to android+jvm KMP and lift balance-path composables Lifts the nine composables the balance screen and activity feed render (TileButton, TokenCard, TokenCardStack, TokenImageWithName, ViewerAmount, ActivityAmount, CurrencyAppreciationLabel, FlagWithFiat, NewUserTutorial) to commonMain. Everything else stays in androidMain unchanged. Seams: screenHeightPx (Configuration on Android, window container on desktop) and ImageRequestPlaceholders for Coil placeholders. Flags load through painterForResId. app:core exposes its res/ as Compose resources and gains CoreStrings/CoreDrawables id objects for the lifted files. opencode-compose moves to KMP with LocalExchange in commonMain. --- apps/flipcash/core-ui/build.gradle.kts | 62 +++++++++++++++---- .../ui/NavigationBarAvatarScreenshotTest.kt | 0 .../app/core/ui/NavigationBarLongPressTest.kt | 0 .../core/ui/TokenCardStackPlacementTest.kt | 0 .../ui/TokenCardWatermarkScreenshotTest.kt | 0 .../flipcash/app/core/ui/ViewerAmountTest.kt | 0 .../app/core/ui/flow/FlowProgressTest.kt | 0 .../ui/previews/AppBarTitlePreviews.kt | 0 .../internal/ui/previews/ButtonPreviews.kt | 0 .../internal/ui/previews/CalloutPreviews.kt | 0 .../flipcash/app/core/ui/AmountWithKeypad.kt | 0 .../app/core/ui/BrandedGradientIcon.kt | 0 .../com/flipcash/app/core/ui/Callout.kt | 0 .../flipcash/app/core/ui/ConfirmationStyle.kt | 0 .../flipcash/app/core/ui/ConversionGraphic.kt | 0 .../flipcash/app/core/ui/CurrencyHolder.kt | 0 .../com/flipcash/app/core/ui/DeviceFrame.kt | 0 .../flipcash/app/core/ui/DisplayTextInput.kt | 0 .../ui/ImageRequestPlaceholders.android.kt | 8 +++ .../com/flipcash/app/core/ui/NavigationBar.kt | 0 .../app/core/ui/NotificationPreview.kt | 0 .../flipcash/app/core/ui/NotifyButtonLabel.kt | 0 .../flipcash/app/core/ui/ReceiptLineItem.kt | 0 .../app/core/ui/ScreenHeight.android.kt | 11 ++++ .../com/flipcash/app/core/ui/Skeleton.kt | 0 .../com/flipcash/app/core/ui/SwitchPreview.kt | 0 .../flipcash/app/core/ui/TokenBalanceRow.kt | 0 .../app/core/ui/TokenSelectionPill.kt | 0 .../flipcash/app/core/ui/flow/FlowProgress.kt | 0 .../flipcash/app/core/ui/flow/FlowStepBar.kt | 0 .../app/core/ui/flow/FlowStepBarController.kt | 0 .../flipcash/app/core/ui/flow/FlowStepper.kt | 0 .../app/core/ui/flow/SteppedFlowScaffold.kt | 2 +- .../ui/processing/FlowProcessingScreen.kt | 0 .../processing/ProcessingLoadingIndicator.kt | 0 .../ui/transitions/CardExpandTransition.kt | 0 .../core/ui/transitions/FocusWhenSettled.kt | 0 .../core/ui/transitions/SharedTransitions.kt | 0 .../flipcash/app/core/ui/ActivityAmount.kt | 0 .../app/core/ui/CurrencyAppreciationLabel.kt | 12 ++-- .../com/flipcash/app/core/ui/FlagWithFiat.kt | 4 +- .../app/core/ui/ImageRequestPlaceholders.kt | 6 ++ .../com/flipcash/app/core/ui/ScreenHeight.kt | 7 +++ .../com/flipcash/app/core/ui/TileButton.kt | 0 .../com/flipcash/app/core/ui/TokenCard.kt | 0 .../flipcash/app/core/ui/TokenCardStack.kt | 5 +- .../app/core/ui/TokenImageWithName.kt | 4 +- .../com/flipcash/app/core/ui/ViewerAmount.kt | 0 .../app/core/ui/onboarding/NewUserTutorial.kt | 21 ++++--- .../core/ui/ImageRequestPlaceholders.jvm.kt | 6 ++ .../flipcash/app/core/ui/ScreenHeight.jvm.kt | 7 +++ apps/flipcash/core/build.gradle.kts | 14 +++++ .../core/resources/CoreResources.android.kt | 22 +++++++ .../flipcash/app/core/money/FormatUtils.kt | 0 .../app/core/resources/CoreResources.kt | 25 ++++++++ .../app/core/resources/CoreResources.jvm.kt | 33 ++++++++++ services/opencode-compose/build.gradle.kts | 29 +++------ .../getcode/opencode/compose/ExchangeStub.kt | 0 .../com/getcode/opencode/compose/Exchange.kt | 0 59 files changed, 222 insertions(+), 56 deletions(-) rename apps/flipcash/core-ui/src/{test => androidHostTest}/kotlin/com/flipcash/app/core/ui/NavigationBarAvatarScreenshotTest.kt (100%) rename apps/flipcash/core-ui/src/{test => androidHostTest}/kotlin/com/flipcash/app/core/ui/NavigationBarLongPressTest.kt (100%) rename apps/flipcash/core-ui/src/{test => androidHostTest}/kotlin/com/flipcash/app/core/ui/TokenCardStackPlacementTest.kt (100%) rename apps/flipcash/core-ui/src/{test => androidHostTest}/kotlin/com/flipcash/app/core/ui/TokenCardWatermarkScreenshotTest.kt (100%) rename apps/flipcash/core-ui/src/{test => androidHostTest}/kotlin/com/flipcash/app/core/ui/ViewerAmountTest.kt (100%) rename apps/flipcash/core-ui/src/{test => androidHostTest}/kotlin/com/flipcash/app/core/ui/flow/FlowProgressTest.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/internal/ui/previews/AppBarTitlePreviews.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/internal/ui/previews/ButtonPreviews.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/internal/ui/previews/CalloutPreviews.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/AmountWithKeypad.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/BrandedGradientIcon.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/Callout.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/ConfirmationStyle.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/ConversionGraphic.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/CurrencyHolder.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/DeviceFrame.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/DisplayTextInput.kt (100%) create mode 100644 apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.android.kt rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/NavigationBar.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/NotificationPreview.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/NotifyButtonLabel.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/ReceiptLineItem.kt (100%) create mode 100644 apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.android.kt rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/Skeleton.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/SwitchPreview.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/TokenBalanceRow.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/TokenSelectionPill.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/flow/FlowProgress.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/flow/FlowStepBar.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/flow/FlowStepBarController.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/flow/FlowStepper.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/flow/SteppedFlowScaffold.kt (98%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/processing/FlowProcessingScreen.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/processing/ProcessingLoadingIndicator.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/transitions/CardExpandTransition.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/transitions/FocusWhenSettled.kt (100%) rename apps/flipcash/core-ui/src/{main => androidMain}/kotlin/com/flipcash/app/core/ui/transitions/SharedTransitions.kt (100%) rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/ActivityAmount.kt (100%) rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt (90%) rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/FlagWithFiat.kt (95%) create mode 100644 apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.kt create mode 100644 apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.kt rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/TileButton.kt (100%) rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/TokenCard.kt (100%) rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt (98%) rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/TokenImageWithName.kt (96%) rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/ViewerAmount.kt (100%) rename apps/flipcash/core-ui/src/{main => commonMain}/kotlin/com/flipcash/app/core/ui/onboarding/NewUserTutorial.kt (88%) create mode 100644 apps/flipcash/core-ui/src/jvmMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.jvm.kt create mode 100644 apps/flipcash/core-ui/src/jvmMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.jvm.kt create mode 100644 apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/money/FormatUtils.kt (100%) create mode 100644 apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt create mode 100644 apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt rename services/opencode-compose/src/{main => androidMain}/kotlin/com/getcode/opencode/compose/ExchangeStub.kt (100%) rename services/opencode-compose/src/{main => commonMain}/kotlin/com/getcode/opencode/compose/Exchange.kt (100%) diff --git a/apps/flipcash/core-ui/build.gradle.kts b/apps/flipcash/core-ui/build.gradle.kts index 8e3f3d0dff..f457abba5f 100644 --- a/apps/flipcash/core-ui/build.gradle.kts +++ b/apps/flipcash/core-ui/build.gradle.kts @@ -1,18 +1,58 @@ plugins { - alias(libs.plugins.flipcash.android.feature) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlin.ksp) } -android { - namespace = "${Gradle.flipcashNamespace}.core.ui" -} +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.core.ui" + androidResources { + enable = true + } + } -dependencies { - implementation(project(":apps:flipcash:shared:featureflags")) + sourceSets { + commonMain.dependencies { + implementation(project(":apps:flipcash:core")) + implementation(project(":services:opencode")) + implementation(project(":services:opencode-compose")) + implementation(project(":libs:encryption:keys")) + implementation(project(":ui:components")) + implementation(project(":ui:resources")) + implementation(project(":ui:theme")) - implementation(libs.compose.material3) - implementation(libs.bundles.haze) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.material) + implementation(libs.jetbrains.compose.material3) + implementation(libs.jetbrains.compose.material.icons.extended) + implementation(libs.coil3) + } + androidMain.dependencies { + // What flipcash.android.feature supplied before the move to KMP. + implementation(libs.bundles.hilt) + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(libs.bundles.compose) + implementation(libs.rinku.compose) + implementation(project(":libs:coroutines")) + implementation(project(":libs:logging")) + implementation(project(":ui:navigation")) + implementation(project(":apps:flipcash:shared:featureflags")) - testImplementation(kotlin("test")) - testImplementation(libs.bundles.unit.testing) - testImplementation(libs.robolectric) + implementation(libs.compose.material3) + implementation(libs.bundles.haze) + } + getByName("androidHostTest").dependencies { + implementation(kotlin("test")) + implementation(libs.bundles.unit.testing) + implementation(libs.robolectric) + } + } +} + +dependencies { + add("kspAndroid", libs.hilt.android.compiler) + add("kspAndroid", libs.hilt.compiler) } diff --git a/apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/NavigationBarAvatarScreenshotTest.kt b/apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/NavigationBarAvatarScreenshotTest.kt similarity index 100% rename from apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/NavigationBarAvatarScreenshotTest.kt rename to apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/NavigationBarAvatarScreenshotTest.kt diff --git a/apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/NavigationBarLongPressTest.kt b/apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/NavigationBarLongPressTest.kt similarity index 100% rename from apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/NavigationBarLongPressTest.kt rename to apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/NavigationBarLongPressTest.kt diff --git a/apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/TokenCardStackPlacementTest.kt b/apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/TokenCardStackPlacementTest.kt similarity index 100% rename from apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/TokenCardStackPlacementTest.kt rename to apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/TokenCardStackPlacementTest.kt diff --git a/apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/TokenCardWatermarkScreenshotTest.kt b/apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/TokenCardWatermarkScreenshotTest.kt similarity index 100% rename from apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/TokenCardWatermarkScreenshotTest.kt rename to apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/TokenCardWatermarkScreenshotTest.kt diff --git a/apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/ViewerAmountTest.kt b/apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/ViewerAmountTest.kt similarity index 100% rename from apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/ViewerAmountTest.kt rename to apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/ViewerAmountTest.kt diff --git a/apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/flow/FlowProgressTest.kt b/apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/flow/FlowProgressTest.kt similarity index 100% rename from apps/flipcash/core-ui/src/test/kotlin/com/flipcash/app/core/ui/flow/FlowProgressTest.kt rename to apps/flipcash/core-ui/src/androidHostTest/kotlin/com/flipcash/app/core/ui/flow/FlowProgressTest.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/internal/ui/previews/AppBarTitlePreviews.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/internal/ui/previews/AppBarTitlePreviews.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/internal/ui/previews/AppBarTitlePreviews.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/internal/ui/previews/AppBarTitlePreviews.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/internal/ui/previews/ButtonPreviews.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/internal/ui/previews/ButtonPreviews.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/internal/ui/previews/ButtonPreviews.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/internal/ui/previews/ButtonPreviews.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/internal/ui/previews/CalloutPreviews.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/internal/ui/previews/CalloutPreviews.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/internal/ui/previews/CalloutPreviews.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/internal/ui/previews/CalloutPreviews.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/AmountWithKeypad.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/AmountWithKeypad.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/AmountWithKeypad.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/AmountWithKeypad.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/BrandedGradientIcon.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/BrandedGradientIcon.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/BrandedGradientIcon.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/BrandedGradientIcon.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/Callout.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/Callout.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/Callout.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/Callout.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ConfirmationStyle.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ConfirmationStyle.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ConfirmationStyle.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ConfirmationStyle.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ConversionGraphic.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ConversionGraphic.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ConversionGraphic.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ConversionGraphic.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/CurrencyHolder.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/CurrencyHolder.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/CurrencyHolder.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/CurrencyHolder.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/DeviceFrame.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/DeviceFrame.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/DeviceFrame.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/DeviceFrame.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/DisplayTextInput.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/DisplayTextInput.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/DisplayTextInput.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/DisplayTextInput.kt diff --git a/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.android.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.android.kt new file mode 100644 index 0000000000..89d57a79c8 --- /dev/null +++ b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.android.kt @@ -0,0 +1,8 @@ +package com.flipcash.app.core.ui + +import coil3.request.ImageRequest +import coil3.request.error +import com.getcode.ui.components.R + +internal actual fun ImageRequest.Builder.errorPlaceholderUser(): ImageRequest.Builder = + error(R.drawable.ic_placeholder_user) diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/NavigationBar.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NavigationBar.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/NavigationBar.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NotificationPreview.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/NotificationPreview.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NotificationPreview.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/NotificationPreview.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NotifyButtonLabel.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/NotifyButtonLabel.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/NotifyButtonLabel.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/NotifyButtonLabel.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ReceiptLineItem.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ReceiptLineItem.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ReceiptLineItem.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ReceiptLineItem.kt diff --git a/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.android.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.android.kt new file mode 100644 index 0000000000..5ec54fe231 --- /dev/null +++ b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.android.kt @@ -0,0 +1,11 @@ +package com.flipcash.app.core.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp + +@Composable +internal actual fun screenHeightPx(): Float = with(LocalDensity.current) { + LocalConfiguration.current.screenHeightDp.dp.toPx() +} diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/Skeleton.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/Skeleton.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/Skeleton.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/Skeleton.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/SwitchPreview.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/SwitchPreview.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/SwitchPreview.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/SwitchPreview.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenBalanceRow.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/TokenBalanceRow.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenBalanceRow.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/TokenBalanceRow.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenSelectionPill.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/TokenSelectionPill.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenSelectionPill.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/TokenSelectionPill.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/FlowProgress.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/FlowProgress.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/FlowProgress.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/FlowProgress.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/FlowStepBar.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/FlowStepBar.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/FlowStepBar.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/FlowStepBar.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/FlowStepBarController.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/FlowStepBarController.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/FlowStepBarController.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/FlowStepBarController.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/FlowStepper.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/FlowStepper.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/FlowStepper.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/FlowStepper.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/SteppedFlowScaffold.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/SteppedFlowScaffold.kt similarity index 98% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/SteppedFlowScaffold.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/SteppedFlowScaffold.kt index 2f84fd1be8..e1b07675dd 100644 --- a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/flow/SteppedFlowScaffold.kt +++ b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/flow/SteppedFlowScaffold.kt @@ -1,6 +1,6 @@ package com.flipcash.app.core.ui.flow -import android.os.Parcelable +import com.getcode.libs.parcelable.Parcelable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/processing/FlowProcessingScreen.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/processing/FlowProcessingScreen.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/processing/FlowProcessingScreen.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/processing/FlowProcessingScreen.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/processing/ProcessingLoadingIndicator.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/processing/ProcessingLoadingIndicator.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/processing/ProcessingLoadingIndicator.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/processing/ProcessingLoadingIndicator.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/transitions/CardExpandTransition.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/transitions/CardExpandTransition.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/transitions/CardExpandTransition.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/transitions/CardExpandTransition.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/transitions/FocusWhenSettled.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/transitions/FocusWhenSettled.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/transitions/FocusWhenSettled.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/transitions/FocusWhenSettled.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/transitions/SharedTransitions.kt b/apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/transitions/SharedTransitions.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/transitions/SharedTransitions.kt rename to apps/flipcash/core-ui/src/androidMain/kotlin/com/flipcash/app/core/ui/transitions/SharedTransitions.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ActivityAmount.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ActivityAmount.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ActivityAmount.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ActivityAmount.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt similarity index 90% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt index c2ad4f5574..bc05434934 100644 --- a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt +++ b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/CurrencyAppreciationLabel.kt @@ -10,15 +10,15 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.flipcash.app.core.money.formattedAppreciation -import com.flipcash.core.R +import com.flipcash.app.core.resources.CoreStrings import com.getcode.opencode.model.financial.Fiat import com.getcode.theme.CodeTheme import com.getcode.theme.White20 import com.getcode.theme.extraSmall import com.getcode.ui.components.text.AnimatedNumberText +import com.getcode.util.resources.stringRes /** Controls the visual treatment of the appreciation badge. */ enum class AppreciationStyle { @@ -88,15 +88,15 @@ fun CurrencyAppreciationLabel( val label = when (style) { AppreciationStyle.Pill -> if (hasAppreciation) { - stringResource(R.string.label_fromAppreciation) + stringRes(CoreStrings.label_fromAppreciation) } else { - stringResource(R.string.label_fromDepreciation) + stringRes(CoreStrings.label_fromDepreciation) } AppreciationStyle.Classic -> if (hasAppreciation) { - stringResource(R.string.label_fromCurrencyAppreciation) + stringRes(CoreStrings.label_fromCurrencyAppreciation) } else { - stringResource(R.string.label_fromCurrencyDepreciation) + stringRes(CoreStrings.label_fromCurrencyDepreciation) } } Text( diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/FlagWithFiat.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/FlagWithFiat.kt similarity index 95% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/FlagWithFiat.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/FlagWithFiat.kt index 83d0712e1b..fe0b57434a 100644 --- a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/FlagWithFiat.kt +++ b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/FlagWithFiat.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource +import com.getcode.util.resources.painterForResId import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp import com.getcode.opencode.compose.LocalExchange @@ -45,7 +45,7 @@ fun FlagWithFiat( .height(iconSize) .width(iconSize) .clip(CircleShape), - painter = painterResource(it), + painter = painterForResId(it), contentDescription = "" ) } diff --git a/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.kt new file mode 100644 index 0000000000..204448d2a8 --- /dev/null +++ b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.kt @@ -0,0 +1,6 @@ +package com.flipcash.app.core.ui + +import coil3.request.ImageRequest + +/** Shows the user placeholder when the request fails. Coil's `error(Int)` overload is Android-only. */ +internal expect fun ImageRequest.Builder.errorPlaceholderUser(): ImageRequest.Builder diff --git a/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.kt new file mode 100644 index 0000000000..b56fa9b7c1 --- /dev/null +++ b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.kt @@ -0,0 +1,7 @@ +package com.flipcash.app.core.ui + +import androidx.compose.runtime.Composable + +/** Screen height in pixels: `Configuration.screenHeightDp` on Android, the window's container height on desktop. */ +@Composable +internal expect fun screenHeightPx(): Float diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TileButton.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TileButton.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TileButton.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TileButton.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenCard.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TokenCard.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenCard.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TokenCard.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt similarity index 98% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt index 0bde9a7353..7446efcdcf 100644 --- a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt +++ b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TokenCardStack.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.util.lerp import androidx.compose.ui.unit.Dp @@ -132,9 +131,7 @@ fun TokenCardStack( // Screen height, so cards below the selected one travel off the bottom edge (read live in the reorg // layer; changes rarely). - val windowHeightPx = with(LocalDensity.current) { - LocalConfiguration.current.screenHeightDp.dp.toPx() - } + val windowHeightPx = screenHeightPx() Layout( modifier = modifier diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenImageWithName.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TokenImageWithName.kt similarity index 96% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenImageWithName.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TokenImageWithName.kt index 9425ab32c6..dee6f64aef 100644 --- a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/TokenImageWithName.kt +++ b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/TokenImageWithName.kt @@ -19,10 +19,8 @@ import coil3.compose.AsyncImage import coil3.compose.LocalPlatformContext import coil3.request.ImageRequest import coil3.request.crossfade -import coil3.request.error import com.getcode.opencode.model.financial.Token import com.getcode.theme.CodeTheme -import com.getcode.ui.components.R @Composable fun TokenIconWithName( @@ -106,7 +104,7 @@ fun TokenIcon( model = ImageRequest.Builder(LocalPlatformContext.current) .data(image) .crossfade(false) - .error(R.drawable.ic_placeholder_user) + .errorPlaceholderUser() .build(), contentDescription = null, contentScale = ContentScale.Crop, diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ViewerAmount.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ViewerAmount.kt similarity index 100% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/ViewerAmount.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/ViewerAmount.kt diff --git a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/onboarding/NewUserTutorial.kt b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/onboarding/NewUserTutorial.kt similarity index 88% rename from apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/onboarding/NewUserTutorial.kt rename to apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/onboarding/NewUserTutorial.kt index cb9c858261..cb1a56b6c8 100644 --- a/apps/flipcash/core-ui/src/main/kotlin/com/flipcash/app/core/ui/onboarding/NewUserTutorial.kt +++ b/apps/flipcash/core-ui/src/commonMain/kotlin/com/flipcash/app/core/ui/onboarding/NewUserTutorial.kt @@ -23,14 +23,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.rememberVectorPainter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastForEach -import com.flipcash.core.R +import com.flipcash.app.core.resources.CoreDrawables +import com.flipcash.app.core.resources.CoreStrings import com.getcode.theme.CodeTheme import com.getcode.theme.extraSmall +import com.getcode.util.resources.painterForResId +import com.getcode.util.resources.stringRes /** * A row in a "what's left to do" checklist. @@ -52,20 +53,20 @@ sealed interface TutorialItem { class AddMoney(override val isCompleted: Boolean) : Wallet { override val title: String - @Composable get() = stringResource(R.string.title_addMoney) + @Composable get() = stringRes(CoreStrings.title_addMoney) override val description: String - @Composable get() = stringResource(R.string.subtitle_addMoney) + @Composable get() = stringRes(CoreStrings.subtitle_addMoney) override val icon: Painter @Composable get() = rememberVectorPainter(Icons.Outlined.AddCircleOutline) } class ScanTipCard(override val isCompleted: Boolean) : Wallet { override val title: String - @Composable get() = stringResource(R.string.title_scanTipCard) + @Composable get() = stringRes(CoreStrings.title_scanTipCard) override val description: String - @Composable get() = stringResource(R.string.subtitle_scanTipCard) + @Composable get() = stringRes(CoreStrings.subtitle_scanTipCard) override val icon: Painter - @Composable get() = painterResource(R.drawable.ic_nav_tipcard) + @Composable get() = painterForResId(CoreDrawables.ic_nav_tipcard) } } @@ -136,7 +137,7 @@ private fun OnboardingItemRow( Image( modifier = Modifier.align(Alignment.Top).size(24.dp), painter = if (item.isCompleted) { - painterResource(R.drawable.ic_checked_green) + painterForResId(CoreDrawables.ic_checked_green) } else { item.icon }, @@ -161,7 +162,7 @@ private fun OnboardingItemRow( } Icon( modifier = Modifier.align(Alignment.CenterVertically).size(16.dp), - painter = painterResource(R.drawable.ic_chevron_right), + painter = painterForResId(CoreDrawables.ic_chevron_right), tint = CodeTheme.colors.textSecondary, contentDescription = null ) diff --git a/apps/flipcash/core-ui/src/jvmMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.jvm.kt b/apps/flipcash/core-ui/src/jvmMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.jvm.kt new file mode 100644 index 0000000000..5023301a00 --- /dev/null +++ b/apps/flipcash/core-ui/src/jvmMain/kotlin/com/flipcash/app/core/ui/ImageRequestPlaceholders.jvm.kt @@ -0,0 +1,6 @@ +package com.flipcash.app.core.ui + +import coil3.request.ImageRequest + +// TODO(desktop): decode ic_placeholder_user from Compose resources into a coil Image. +internal actual fun ImageRequest.Builder.errorPlaceholderUser(): ImageRequest.Builder = this diff --git a/apps/flipcash/core-ui/src/jvmMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.jvm.kt b/apps/flipcash/core-ui/src/jvmMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.jvm.kt new file mode 100644 index 0000000000..9246aacac8 --- /dev/null +++ b/apps/flipcash/core-ui/src/jvmMain/kotlin/com/flipcash/app/core/ui/ScreenHeight.jvm.kt @@ -0,0 +1,7 @@ +package com.flipcash.app.core.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalWindowInfo + +@Composable +internal actual fun screenHeightPx(): Float = LocalWindowInfo.current.containerSize.height.toFloat() diff --git a/apps/flipcash/core/build.gradle.kts b/apps/flipcash/core/build.gradle.kts index 3deb1a4c65..52a65baa26 100644 --- a/apps/flipcash/core/build.gradle.kts +++ b/apps/flipcash/core/build.gradle.kts @@ -1,6 +1,9 @@ +import com.getcode.buildlogic.resources.androidResAsComposeResources + plugins { alias(libs.plugins.flipcash.kmp.app.library) alias(libs.plugins.compose.compiler) + alias(libs.plugins.jetbrains.compose) alias(libs.plugins.kotlin.ksp) } @@ -22,6 +25,8 @@ kotlin { implementation(libs.kotlinx.serialization.json) implementation(libs.jetbrains.compose.runtime) implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.components.resources) + implementation(project(":ui:resources")) api(project(":libs:reporting")) api(project(":services:flipcash")) @@ -72,6 +77,15 @@ kotlin { } } +compose.resources { + publicResClass = true + packageOfResClass = "${Gradle.flipcashNamespace}.core.resources" + customDirectory( + sourceSetName = "commonMain", + directoryProvider = androidResAsComposeResources(), + ) +} + dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt new file mode 100644 index 0000000000..945ba5768c --- /dev/null +++ b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt @@ -0,0 +1,22 @@ +package com.flipcash.app.core.resources + +import com.flipcash.core.R + +@Suppress("PropertyName") +actual object CoreStrings { + actual val title_addMoney: Int = R.string.title_addMoney + actual val subtitle_addMoney: Int = R.string.subtitle_addMoney + actual val title_scanTipCard: Int = R.string.title_scanTipCard + actual val subtitle_scanTipCard: Int = R.string.subtitle_scanTipCard + actual val label_fromAppreciation: Int = R.string.label_fromAppreciation + actual val label_fromDepreciation: Int = R.string.label_fromDepreciation + actual val label_fromCurrencyAppreciation: Int = R.string.label_fromCurrencyAppreciation + actual val label_fromCurrencyDepreciation: Int = R.string.label_fromCurrencyDepreciation +} + +@Suppress("PropertyName") +actual object CoreDrawables { + actual val ic_nav_tipcard: Int = R.drawable.ic_nav_tipcard + actual val ic_checked_green: Int = R.drawable.ic_checked_green + actual val ic_chevron_right: Int = R.drawable.ic_chevron_right +} diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/money/FormatUtils.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/money/FormatUtils.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/money/FormatUtils.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/money/FormatUtils.kt diff --git a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt new file mode 100644 index 0000000000..61f1fda6ba --- /dev/null +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt @@ -0,0 +1,25 @@ +package com.flipcash.app.core.resources + +/** + * `Int` IDs for the `app:core` resources that common UI hands to [com.getcode.util.resources.stringRes] + * and [com.getcode.util.resources.painterForResId]. Android returns the `R` IDs; desktop registers the + * generated Compose resources. + */ +@Suppress("PropertyName") +expect object CoreStrings { + val title_addMoney: Int + val subtitle_addMoney: Int + val title_scanTipCard: Int + val subtitle_scanTipCard: Int + val label_fromAppreciation: Int + val label_fromDepreciation: Int + val label_fromCurrencyAppreciation: Int + val label_fromCurrencyDepreciation: Int +} + +@Suppress("PropertyName") +expect object CoreDrawables { + val ic_nav_tipcard: Int + val ic_checked_green: Int + val ic_chevron_right: Int +} diff --git a/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt new file mode 100644 index 0000000000..0861caa072 --- /dev/null +++ b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt @@ -0,0 +1,33 @@ +package com.flipcash.app.core.resources + +import com.flipcash.core.resources.Res +import com.flipcash.core.resources.ic_checked_green +import com.flipcash.core.resources.ic_nav_tipcard +import com.flipcash.core.resources.label_fromAppreciation +import com.flipcash.core.resources.label_fromCurrencyAppreciation +import com.flipcash.core.resources.label_fromCurrencyDepreciation +import com.flipcash.core.resources.label_fromDepreciation +import com.flipcash.core.resources.subtitle_addMoney +import com.flipcash.core.resources.subtitle_scanTipCard +import com.flipcash.core.resources.title_addMoney +import com.flipcash.core.resources.title_scanTipCard +import com.getcode.util.resources.ComposeResourceIds + +@Suppress("PropertyName") +actual object CoreStrings { + actual val title_addMoney: Int = ComposeResourceIds.register(Res.string.title_addMoney) + actual val subtitle_addMoney: Int = ComposeResourceIds.register(Res.string.subtitle_addMoney) + actual val title_scanTipCard: Int = ComposeResourceIds.register(Res.string.title_scanTipCard) + actual val subtitle_scanTipCard: Int = ComposeResourceIds.register(Res.string.subtitle_scanTipCard) + actual val label_fromAppreciation: Int = ComposeResourceIds.register(Res.string.label_fromAppreciation) + actual val label_fromDepreciation: Int = ComposeResourceIds.register(Res.string.label_fromDepreciation) + actual val label_fromCurrencyAppreciation: Int = ComposeResourceIds.register(Res.string.label_fromCurrencyAppreciation) + actual val label_fromCurrencyDepreciation: Int = ComposeResourceIds.register(Res.string.label_fromCurrencyDepreciation) +} + +@Suppress("PropertyName") +actual object CoreDrawables { + actual val ic_nav_tipcard: Int = ComposeResourceIds.register(Res.drawable.ic_nav_tipcard) + actual val ic_checked_green: Int = ComposeResourceIds.register(Res.drawable.ic_checked_green) + actual val ic_chevron_right: Int = checkNotNull(ComposeResourceIds.drawableId("ic_chevron_right")) +} diff --git a/services/opencode-compose/build.gradle.kts b/services/opencode-compose/build.gradle.kts index f3c32e5221..fb22ba0b69 100644 --- a/services/opencode-compose/build.gradle.kts +++ b/services/opencode-compose/build.gradle.kts @@ -1,26 +1,17 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) } -android { - namespace = "${Gradle.codeNamespace}.services.opencode.compose" - - defaultConfig { - consumerProguardFiles("consumer-rules.pro") - - buildConfigField("Boolean", "NOTIFY_ERRORS", "false") - buildConfigField( - "String", - "GOOGLE_CLOUD_PROJECT_NUMBER", - "\"${tryReadProperty(rootProject.rootDir, "GOOGLE_CLOUD_PROJECT_NUMBER", "-1L")}\"" - ) +kotlin { + android { + namespace = "${Gradle.codeNamespace}.services.opencode.compose" } - buildFeatures { - buildConfig = true + sourceSets { + commonMain.dependencies { + api(project(":services:opencode")) + implementation(libs.jetbrains.compose.runtime) + } } } - -dependencies { - api(project(":services:opencode")) -} diff --git a/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/ExchangeStub.kt b/services/opencode-compose/src/androidMain/kotlin/com/getcode/opencode/compose/ExchangeStub.kt similarity index 100% rename from services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/ExchangeStub.kt rename to services/opencode-compose/src/androidMain/kotlin/com/getcode/opencode/compose/ExchangeStub.kt diff --git a/services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/Exchange.kt b/services/opencode-compose/src/commonMain/kotlin/com/getcode/opencode/compose/Exchange.kt similarity index 100% rename from services/opencode-compose/src/main/kotlin/com/getcode/opencode/compose/Exchange.kt rename to services/opencode-compose/src/commonMain/kotlin/com/getcode/opencode/compose/Exchange.kt From 61c39c8c5f588091e39fd92262c016aa507843cf Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:52:19 -0400 Subject: [PATCH 09/17] build(common-ui): move common-ui to android+jvm KMP and lift ContactAvatar BlurHash decodes to pixels in common code; each platform wraps them (Android still builds the same ARGB_8888 Bitmap, and keeps BlurHash.decode as an extension). ContactAvatar uses Coil's LocalPlatformContext and passes the URL string to Coil, which maps it as it did the Uri. app:core's media resolver and TimeProvider move to common; the Coil-backed ImageCachePresence stays Android. --- apps/flipcash/core/build.gradle.kts | 1 + ...ePresence.kt => CoilImageCachePresence.kt} | 9 --- .../app/core/media/ImageCachePresence.kt | 10 ++++ .../app/core/media/MediaUrlResolver.kt | 0 .../app/core/media/RememberMediaUrl.kt | 0 .../app/core/time/FakeTimeProvider.kt | 0 .../flipcash/app/core/time/TimeProvider.kt | 0 .../flipcash/app/blob/BlurHashEncoderTest.kt | 1 + .../shared/common-ui/build.gradle.kts | 58 +++++++++++++------ .../ui/BlurHashAverageColorVectorTest.kt | 0 .../flipcash/shared/common/ui/BlurHashTest.kt | 0 .../common/ui/profile/JoinedLabelTest.kt | 0 .../resources/blurhash_average.json | 0 .../shared/common/ui/ImageBitmaps.android.kt | 16 +++++ .../shared/common/ui/amount/AmountChip.kt | 0 .../ui/profile/BalanceRequirementsCard.kt | 0 .../ui/profile/FeaturedGroupsSection.kt | 0 .../common/ui/profile/GroupChattingGrid.kt | 0 .../shared/common/ui/profile/JoinedLabel.kt | 0 .../common/ui/profile/ProfileActionButton.kt | 0 .../shared/common/ui/profile/ProfileCover.kt | 0 .../common/ui/profile/ProfileEditFields.kt | 0 .../shared/common/ui/profile/ProfileHeader.kt | 0 .../ui/profile/ProfilePinnedActionBar.kt | 0 .../common/ui/profile/ProfileStatsCard.kt | 0 .../common/ui/profile/ProfileStatusChip.kt | 0 .../common/ui/profile/ProfileTopScrollEdge.kt | 0 .../res/values/strings.xml | 0 .../com/flipcash/shared/common/ui/BlurHash.kt | 31 ++++++---- .../shared/common/ui/ContactAvatar.kt | 34 +++++------ .../shared/common/ui/ImageBitmaps.jvm.kt | 26 +++++++++ 31 files changed, 129 insertions(+), 57 deletions(-) rename apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/{ImageCachePresence.kt => CoilImageCachePresence.kt} (78%) create mode 100644 apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/media/ImageCachePresence.kt rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/media/MediaUrlResolver.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/media/RememberMediaUrl.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/time/FakeTimeProvider.kt (100%) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/time/TimeProvider.kt (100%) rename apps/flipcash/shared/common-ui/src/{test => androidHostTest}/kotlin/com/flipcash/shared/common/ui/BlurHashAverageColorVectorTest.kt (100%) rename apps/flipcash/shared/common-ui/src/{test => androidHostTest}/kotlin/com/flipcash/shared/common/ui/BlurHashTest.kt (100%) rename apps/flipcash/shared/common-ui/src/{test => androidHostTest}/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabelTest.kt (100%) rename apps/flipcash/shared/common-ui/src/{test => androidHostTest}/resources/blurhash_average.json (100%) create mode 100644 apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/ImageBitmaps.android.kt rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/amount/AmountChip.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/BalanceRequirementsCard.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/FeaturedGroupsSection.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/GroupChattingGrid.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabel.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/ProfileActionButton.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/ProfileCover.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/ProfileEditFields.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/ProfileHeader.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatsCard.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatusChip.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/kotlin/com/flipcash/shared/common/ui/profile/ProfileTopScrollEdge.kt (100%) rename apps/flipcash/shared/common-ui/src/{main => androidMain}/res/values/strings.xml (100%) rename apps/flipcash/shared/common-ui/src/{main => commonMain}/kotlin/com/flipcash/shared/common/ui/BlurHash.kt (83%) rename apps/flipcash/shared/common-ui/src/{main => commonMain}/kotlin/com/flipcash/shared/common/ui/ContactAvatar.kt (94%) create mode 100644 apps/flipcash/shared/common-ui/src/jvmMain/kotlin/com/flipcash/shared/common/ui/ImageBitmaps.jvm.kt diff --git a/apps/flipcash/core/build.gradle.kts b/apps/flipcash/core/build.gradle.kts index 52a65baa26..d28cbdf678 100644 --- a/apps/flipcash/core/build.gradle.kts +++ b/apps/flipcash/core/build.gradle.kts @@ -18,6 +18,7 @@ kotlin { sourceSets { commonMain.dependencies { implementation(project(":libs:parcelable")) + implementation(libs.javax.inject) api(project(":ui:navigation")) api(project(":ui:core")) api(libs.navigation3.runtime) diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/ImageCachePresence.kt b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/CoilImageCachePresence.kt similarity index 78% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/ImageCachePresence.kt rename to apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/CoilImageCachePresence.kt index 8c8dcff514..daa966423c 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/ImageCachePresence.kt +++ b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/CoilImageCachePresence.kt @@ -12,15 +12,6 @@ import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton -/** - * Whether the image loader already holds the bytes filed under a stable cache key. Blob renditions - * are cached by their durable blob id rather than their download URL, so a hit here means the URL - * will never be dereferenced — and an expired one costs nothing. - */ -interface ImageCachePresence { - suspend fun holds(cacheKey: String): Boolean -} - @Singleton class CoilImageCachePresence @Inject constructor( @param:ApplicationContext private val context: Context, diff --git a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/media/ImageCachePresence.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/media/ImageCachePresence.kt new file mode 100644 index 0000000000..89f0bb336b --- /dev/null +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/media/ImageCachePresence.kt @@ -0,0 +1,10 @@ +package com.flipcash.app.core.media + +/** + * Whether the image loader already holds the bytes filed under a stable cache key. Blob renditions + * are cached by their durable blob id rather than their download URL, so a hit here means the URL + * will never be dereferenced — and an expired one costs nothing. + */ +interface ImageCachePresence { + suspend fun holds(cacheKey: String): Boolean +} diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/MediaUrlResolver.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/media/MediaUrlResolver.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/MediaUrlResolver.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/media/MediaUrlResolver.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/RememberMediaUrl.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/media/RememberMediaUrl.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/media/RememberMediaUrl.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/media/RememberMediaUrl.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/time/FakeTimeProvider.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/time/FakeTimeProvider.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/time/FakeTimeProvider.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/time/FakeTimeProvider.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/time/TimeProvider.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/time/TimeProvider.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/time/TimeProvider.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/time/TimeProvider.kt diff --git a/apps/flipcash/shared/blob/src/test/kotlin/com/flipcash/app/blob/BlurHashEncoderTest.kt b/apps/flipcash/shared/blob/src/test/kotlin/com/flipcash/app/blob/BlurHashEncoderTest.kt index 7d91d6b3da..bca2e93d68 100644 --- a/apps/flipcash/shared/blob/src/test/kotlin/com/flipcash/app/blob/BlurHashEncoderTest.kt +++ b/apps/flipcash/shared/blob/src/test/kotlin/com/flipcash/app/blob/BlurHashEncoderTest.kt @@ -2,6 +2,7 @@ package com.flipcash.app.blob import android.graphics.Bitmap import com.flipcash.shared.common.ui.BlurHash +import com.flipcash.shared.common.ui.decode import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import kotlin.math.abs diff --git a/apps/flipcash/shared/common-ui/build.gradle.kts b/apps/flipcash/shared/common-ui/build.gradle.kts index a55a5b8f81..afa584a830 100644 --- a/apps/flipcash/shared/common-ui/build.gradle.kts +++ b/apps/flipcash/shared/common-ui/build.gradle.kts @@ -1,24 +1,46 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) } -android { - namespace = "${Gradle.flipcashNamespace}.shared.common.ui" -} +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.shared.common.ui" + androidResources { + enable = true + } + } -dependencies { - implementation(project(":apps:flipcash:core")) - implementation(project(":ui:core")) - implementation(project(":ui:components")) - implementation(project(":ui:theme")) - implementation(project(":ui:resources")) - implementation(project(":services:flipcash")) - implementation(project(":libs:datetime")) - implementation(project(":apps:flipcash:shared:theme")) + sourceSets { + commonMain.dependencies { + implementation(project(":apps:flipcash:core")) + implementation(project(":ui:core")) + implementation(project(":ui:theme")) + implementation(project(":services:flipcash")) + implementation(project(":services:opencode")) - implementation(libs.bundles.haze) - implementation(libs.compose.material) - implementation(libs.bundles.haze) - api(libs.compose.material.icons.extended) - testImplementation(libs.robolectric) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.material3) + api(libs.jetbrains.compose.material.icons.extended) + implementation(libs.coil3) + } + androidMain.dependencies { + implementation(project(":ui:components")) + implementation(project(":ui:resources")) + implementation(project(":libs:datetime")) + implementation(project(":apps:flipcash:shared:theme")) + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(libs.bundles.compose) + implementation(libs.bundles.haze) + implementation(libs.compose.material) + api(libs.compose.material.icons.extended) + } + getByName("androidHostTest").dependencies { + implementation(kotlin("test")) + implementation(libs.bundles.unit.testing) + implementation(libs.robolectric) + } + } } diff --git a/apps/flipcash/shared/common-ui/src/test/kotlin/com/flipcash/shared/common/ui/BlurHashAverageColorVectorTest.kt b/apps/flipcash/shared/common-ui/src/androidHostTest/kotlin/com/flipcash/shared/common/ui/BlurHashAverageColorVectorTest.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/test/kotlin/com/flipcash/shared/common/ui/BlurHashAverageColorVectorTest.kt rename to apps/flipcash/shared/common-ui/src/androidHostTest/kotlin/com/flipcash/shared/common/ui/BlurHashAverageColorVectorTest.kt diff --git a/apps/flipcash/shared/common-ui/src/test/kotlin/com/flipcash/shared/common/ui/BlurHashTest.kt b/apps/flipcash/shared/common-ui/src/androidHostTest/kotlin/com/flipcash/shared/common/ui/BlurHashTest.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/test/kotlin/com/flipcash/shared/common/ui/BlurHashTest.kt rename to apps/flipcash/shared/common-ui/src/androidHostTest/kotlin/com/flipcash/shared/common/ui/BlurHashTest.kt diff --git a/apps/flipcash/shared/common-ui/src/test/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabelTest.kt b/apps/flipcash/shared/common-ui/src/androidHostTest/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabelTest.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/test/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabelTest.kt rename to apps/flipcash/shared/common-ui/src/androidHostTest/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabelTest.kt diff --git a/apps/flipcash/shared/common-ui/src/test/resources/blurhash_average.json b/apps/flipcash/shared/common-ui/src/androidHostTest/resources/blurhash_average.json similarity index 100% rename from apps/flipcash/shared/common-ui/src/test/resources/blurhash_average.json rename to apps/flipcash/shared/common-ui/src/androidHostTest/resources/blurhash_average.json diff --git a/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/ImageBitmaps.android.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/ImageBitmaps.android.kt new file mode 100644 index 0000000000..95c2962d21 --- /dev/null +++ b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/ImageBitmaps.android.kt @@ -0,0 +1,16 @@ +package com.flipcash.shared.common.ui + +import android.graphics.Bitmap +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.graphics.asImageBitmap +import coil3.asImage + +internal actual fun argbToImageBitmap(pixels: IntArray, width: Int, height: Int): ImageBitmap = + Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888).asImageBitmap() + +internal actual fun ImageBitmap.asCoilImage(): coil3.Image = asAndroidBitmap().asImage() + +/** Decodes [blurHash] into an Android [Bitmap]; kept for Android callers of the old API. */ +fun BlurHash.decode(blurHash: String?, width: Int, height: Int, punch: Float = 1f): Bitmap? = + decodeImage(blurHash, width, height, punch)?.asAndroidBitmap() diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/amount/AmountChip.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/amount/AmountChip.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/amount/AmountChip.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/amount/AmountChip.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/BalanceRequirementsCard.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/BalanceRequirementsCard.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/BalanceRequirementsCard.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/BalanceRequirementsCard.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/FeaturedGroupsSection.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/FeaturedGroupsSection.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/FeaturedGroupsSection.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/FeaturedGroupsSection.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/GroupChattingGrid.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/GroupChattingGrid.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/GroupChattingGrid.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/GroupChattingGrid.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabel.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabel.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabel.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/JoinedLabel.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileActionButton.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileActionButton.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileActionButton.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileActionButton.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileCover.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileCover.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileCover.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileCover.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileEditFields.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileEditFields.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileEditFields.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileEditFields.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileHeader.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileHeader.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileHeader.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileHeader.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatsCard.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatsCard.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatsCard.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatsCard.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatusChip.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatusChip.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatusChip.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileStatusChip.kt diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileTopScrollEdge.kt b/apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileTopScrollEdge.kt similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfileTopScrollEdge.kt rename to apps/flipcash/shared/common-ui/src/androidMain/kotlin/com/flipcash/shared/common/ui/profile/ProfileTopScrollEdge.kt diff --git a/apps/flipcash/shared/common-ui/src/main/res/values/strings.xml b/apps/flipcash/shared/common-ui/src/androidMain/res/values/strings.xml similarity index 100% rename from apps/flipcash/shared/common-ui/src/main/res/values/strings.xml rename to apps/flipcash/shared/common-ui/src/androidMain/res/values/strings.xml diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/BlurHash.kt b/apps/flipcash/shared/common-ui/src/commonMain/kotlin/com/flipcash/shared/common/ui/BlurHash.kt similarity index 83% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/BlurHash.kt rename to apps/flipcash/shared/common-ui/src/commonMain/kotlin/com/flipcash/shared/common/ui/BlurHash.kt index 30d9cdd11f..91c1c0f23e 100644 --- a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/BlurHash.kt +++ b/apps/flipcash/shared/common-ui/src/commonMain/kotlin/com/flipcash/shared/common/ui/BlurHash.kt @@ -1,9 +1,8 @@ package com.flipcash.shared.common.ui -import android.graphics.Bitmap import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter import kotlin.math.PI @@ -24,10 +23,17 @@ import kotlin.math.withSign object BlurHash { /** - * Decodes [blurHash] into an [width] × [height] [Bitmap], or null if the string is malformed. - * [punch] adjusts contrast (1f = as encoded). + * Decodes [blurHash] into an [width] × [height] [ImageBitmap], or null if the string is + * malformed. [punch] adjusts contrast (1f = as encoded). */ - fun decode(blurHash: String?, width: Int, height: Int, punch: Float = 1f): Bitmap? { + fun decodeImage(blurHash: String?, width: Int, height: Int, punch: Float = 1f): ImageBitmap? = + decodePixels(blurHash, width, height, punch)?.let { argbToImageBitmap(it, width, height) } + + /** + * Decodes [blurHash] into [width] × [height] opaque ARGB pixels, row by row, or null if the + * string is malformed. The platform bitmap wrappers build on this. + */ + internal fun decodePixels(blurHash: String?, width: Int, height: Int, punch: Float = 1f): IntArray? { if (blurHash == null || blurHash.length < 6 || width <= 0 || height <= 0) return null val sizeFlag = decode83(blurHash, 0, 1) ?: return null @@ -44,7 +50,7 @@ object BlurHash { decodeAc(decode83(blurHash, from, from + 2) ?: return null, maxAc * punch) } } - return composeBitmap(width, height, numCompX, numCompY, colors) + return composePixels(width, height, numCompX, numCompY, colors) } /** @@ -103,13 +109,13 @@ object BlurHash { return (srgb * 255f + 0.5f).toInt() } - private fun composeBitmap( + private fun composePixels( width: Int, height: Int, numCompX: Int, numCompY: Int, colors: Array, - ): Bitmap { + ): IntArray { // Precompute the cosine basis for each axis so the inner pixel loop is just multiplies. val cosX = FloatArray(width * numCompX) for (x in 0 until width) { @@ -144,7 +150,7 @@ object BlurHash { (0xFF shl 24) or (linearToSrgb(r) shl 16) or (linearToSrgb(g) shl 8) or linearToSrgb(b) } } - return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888) + return pixels } private const val CHARS = @@ -161,6 +167,9 @@ fun rememberBlurHashPainter( width: Int = 24, height: Int = 24, ): Painter? = remember(blurHash, width, height) { - val bitmap = BlurHash.decode(blurHash, width, height) ?: return@remember null - BitmapPainter(bitmap.asImageBitmap()) + val bitmap = BlurHash.decodeImage(blurHash, width, height) ?: return@remember null + BitmapPainter(bitmap) } + +/** Wraps opaque ARGB [pixels] (row by row) in a platform [ImageBitmap]. */ +internal expect fun argbToImageBitmap(pixels: IntArray, width: Int, height: Int): ImageBitmap diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/ContactAvatar.kt b/apps/flipcash/shared/common-ui/src/commonMain/kotlin/com/flipcash/shared/common/ui/ContactAvatar.kt similarity index 94% rename from apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/ContactAvatar.kt rename to apps/flipcash/shared/common-ui/src/commonMain/kotlin/com/flipcash/shared/common/ui/ContactAvatar.kt index 9af8fde466..ea8d1da7c1 100644 --- a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/ContactAvatar.kt +++ b/apps/flipcash/shared/common-ui/src/commonMain/kotlin/com/flipcash/shared/common/ui/ContactAvatar.kt @@ -1,6 +1,5 @@ package com.flipcash.shared.common.ui -import android.graphics.Bitmap import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -22,20 +21,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.min -import androidx.core.net.toUri -import coil3.asImage import coil3.compose.AsyncImage +import coil3.compose.LocalPlatformContext import coil3.request.ImageRequest import coil3.request.crossfade -import coil3.request.error -import coil3.request.placeholder import com.flipcash.app.core.contacts.DeviceContact import com.flipcash.app.core.media.rememberMediaUrl import com.flipcash.services.models.HandlePrefix @@ -84,11 +79,11 @@ fun ContactAvatar( if (photoUri != null) { var isError by rememberSaveable(photoUri) { mutableStateOf(false) } if (!isError) { - val context = LocalContext.current + val context = LocalPlatformContext.current val request = remember(photoUri) { ImageRequest.Builder(context) .crossfade(true) - .data(photoUri.toUri()) + .data(photoUri) .build() } AsyncImage( @@ -178,11 +173,10 @@ internal fun ProfileAvatar( // while the rendition downloads, what stays on screen while a stale URL is re-minted, and // what's left if the load never succeeds — anything is a better likeness of the person than // the grey Person glyph. - // Kept as the decoded Bitmap rather than either wrapper: the two consumers want different - // ones (Compose's ImageBitmap here, Coil's Image in the request), and decoding is the - // expensive half. + // Kept as the decoded ImageBitmap; the Coil request wraps the same pixels (asCoilImage), and + // decoding is the expensive half. val blurBitmap = remember(image) { - BlurHash.decode(image?.blurhash(), width = 24, height = 24) + BlurHash.decodeImage(image?.blurhash(), width = 24, height = 24) } if (blurred) { // Blocked users are shown intentionally obscured — render the media item's self-contained @@ -206,14 +200,14 @@ internal fun ProfileAvatar( if (image != null && photoUri != null) { val isError = media.hasFailed if (!isError) { - val context = LocalContext.current + val context = LocalPlatformContext.current // Two progressively better placeholders bridge the load so we never flash a blank // gradient while the correctly-sized rendition downloads: // 1. the BlurHash — an instant, self-contained blurred preview, and // 2. the next-smaller rendition — if another surface already cached it (e.g. the // list loaded the 160 this 320 avatar sits above), Coil shows it immediately // (see placeholderMemoryCacheKey) and upgrades in place. - val blurHash = remember(blurBitmap) { blurBitmap?.asImage() } + val blurHash = remember(blurBitmap) { blurBitmap?.asCoilImage() } // Cache identity is the durable blob id, NOT the download URL: the server re-mints // and expires `download_url` on every fetch, so a URL-keyed cache misses on the next // fetch even though the bytes are immutable — every load would re-download and flash @@ -227,7 +221,7 @@ internal fun ProfileAvatar( val request = remember(photoUri, cacheKey, previewKey, blurHash) { ImageRequest.Builder(context) .crossfade(true) - .data(photoUri.toUri()) + .data(photoUri) // Key both caches on the stable blob id (not the ephemeral URL, and not // size) so every avatar load of this rendition shares one entry that // survives URL rotation and app restarts — which is also what lets a @@ -274,10 +268,10 @@ internal fun ProfileAvatar( */ @Composable private fun BoxWithConstraintsScope.BlurHashOr( - blurBitmap: Bitmap?, + blurBitmap: ImageBitmap?, fallback: @Composable BoxWithConstraintsScope.() -> Unit, ) { - val bitmap = remember(blurBitmap) { blurBitmap?.asImageBitmap() } + val bitmap = blurBitmap if (bitmap != null) { Image( bitmap = bitmap, @@ -347,4 +341,6 @@ private fun BoxWithConstraintsScope.InitialsText(displayName: String) { color = CodeTheme.colors.textSecondary, textAlign = TextAlign.Center, ) -} \ No newline at end of file +} +/** Wraps a decoded [ImageBitmap] as a Coil [coil3.Image] for request placeholders. */ +internal expect fun ImageBitmap.asCoilImage(): coil3.Image diff --git a/apps/flipcash/shared/common-ui/src/jvmMain/kotlin/com/flipcash/shared/common/ui/ImageBitmaps.jvm.kt b/apps/flipcash/shared/common-ui/src/jvmMain/kotlin/com/flipcash/shared/common/ui/ImageBitmaps.jvm.kt new file mode 100644 index 0000000000..8702c70ce9 --- /dev/null +++ b/apps/flipcash/shared/common-ui/src/jvmMain/kotlin/com/flipcash/shared/common/ui/ImageBitmaps.jvm.kt @@ -0,0 +1,26 @@ +package com.flipcash.shared.common.ui + +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asComposeImageBitmap +import androidx.compose.ui.graphics.asSkiaBitmap +import coil3.asImage +import org.jetbrains.skia.Bitmap +import org.jetbrains.skia.ColorAlphaType +import org.jetbrains.skia.ImageInfo + +internal actual fun argbToImageBitmap(pixels: IntArray, width: Int, height: Int): ImageBitmap { + // Skia's N32 is BGRA little-endian, which is the in-memory order of an ARGB Int. + val bytes = ByteArray(pixels.size * 4) + pixels.forEachIndexed { i, argb -> + bytes[i * 4] = (argb and 0xFF).toByte() + bytes[i * 4 + 1] = (argb shr 8 and 0xFF).toByte() + bytes[i * 4 + 2] = (argb shr 16 and 0xFF).toByte() + bytes[i * 4 + 3] = (argb ushr 24).toByte() + } + val bitmap = Bitmap() + bitmap.allocPixels(ImageInfo.makeN32(width, height, ColorAlphaType.OPAQUE)) + bitmap.installPixels(bytes) + return bitmap.asComposeImageBitmap() +} + +internal actual fun ImageBitmap.asCoilImage(): coil3.Image = asSkiaBitmap().asImage() From 3d02a3e751f95ec09c12fbbb9c24f1e2efe06446 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:57:40 -0400 Subject: [PATCH 10/17] build(transaction-history): move transaction-history to android+jvm KMP Lift the activity feed coordinator and updater, both mappers and the recent-activity composables to commonMain. String ids go through a TxStrings expect object (R.string on Android, the Compose resource registry on desktop); the 24-hour clock check is an expect seam. NetworkUpdater moves to app:core's commonMain. --- .../core/resources/CoreResources.android.kt | 2 + .../app/core/resources/CoreResources.kt | 2 + .../app/core/updater/NetworkUpdater.kt | 0 .../app/core/resources/CoreResources.jvm.kt | 4 + .../transaction-history/build.gradle.kts | 96 ++++++++++++++---- .../MessageDirectionTest.kt | 0 .../transactionhistory/MintInvolvementTest.kt | 0 .../TransactionDetailsMapperTest.kt | 0 .../TransactionDetailsSamples.kt | 0 .../TransactionDetailsScreenshotTest.kt | 0 .../TransactionItemMapperTest.kt | 0 .../resources/tokens/dollars.webp | Bin .../resources/tokens/jeffy.png | Bin .../ActivityHistoryScreen.kt | 0 .../ActivityHistoryViewModel.kt | 0 .../Is24HourClock.android.kt | 8 ++ .../TransactionDetailsContent.kt | 0 .../resources/TxResources.android.kt | 34 +++++++ .../res/values/strings.xml | 0 .../ActivityFeedCoordinator.kt | 0 .../transactionhistory/ActivityFeedRow.kt | 13 ++- .../transactionhistory/ActivityFeedUpdater.kt | 0 .../transactionhistory/Is24HourClock.kt | 7 ++ .../transactionhistory/MintInvolvement.kt | 0 .../RecentActivitySection.kt | 11 +- .../transactionhistory/ResolvedTransaction.kt | 0 .../TransactionAvatarImage.kt | 0 .../transactionhistory/TransactionDetails.kt | 39 +++---- .../transactionhistory/TransactionListItem.kt | 0 .../TransactionSubtitles.kt | 9 +- .../internal/TransactionDetailsMapper.kt | 0 .../internal/TransactionItemMapper.kt | 10 +- .../resources/TxResources.kt | 37 +++++++ .../transactionhistory/Is24HourClock.jvm.kt | 13 +++ .../resources/TxResources.jvm.kt | 62 +++++++++++ 35 files changed, 286 insertions(+), 61 deletions(-) rename apps/flipcash/core/src/{androidMain => commonMain}/kotlin/com/flipcash/app/core/updater/NetworkUpdater.kt (100%) rename apps/flipcash/shared/transaction-history/src/{test => androidHostTest}/kotlin/com/flipcash/shared/transactionhistory/MessageDirectionTest.kt (100%) rename apps/flipcash/shared/transaction-history/src/{test => androidHostTest}/kotlin/com/flipcash/shared/transactionhistory/MintInvolvementTest.kt (100%) rename apps/flipcash/shared/transaction-history/src/{test => androidHostTest}/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsMapperTest.kt (100%) rename apps/flipcash/shared/transaction-history/src/{test => androidHostTest}/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsSamples.kt (100%) rename apps/flipcash/shared/transaction-history/src/{test => androidHostTest}/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsScreenshotTest.kt (100%) rename apps/flipcash/shared/transaction-history/src/{test => androidHostTest}/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt (100%) rename apps/flipcash/shared/transaction-history/src/{test => androidHostTest}/resources/tokens/dollars.webp (100%) rename apps/flipcash/shared/transaction-history/src/{test => androidHostTest}/resources/tokens/jeffy.png (100%) rename apps/flipcash/shared/transaction-history/src/{main => androidMain}/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryScreen.kt (100%) rename apps/flipcash/shared/transaction-history/src/{main => androidMain}/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryViewModel.kt (100%) create mode 100644 apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.android.kt rename apps/flipcash/shared/transaction-history/src/{main => androidMain}/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsContent.kt (100%) create mode 100644 apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.android.kt rename apps/flipcash/shared/transaction-history/src/{main => androidMain}/res/values/strings.xml (100%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedCoordinator.kt (100%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedRow.kt (91%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedUpdater.kt (100%) create mode 100644 apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.kt rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/MintInvolvement.kt (100%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/RecentActivitySection.kt (89%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/ResolvedTransaction.kt (100%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/TransactionAvatarImage.kt (100%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/TransactionDetails.kt (84%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/TransactionListItem.kt (100%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/TransactionSubtitles.kt (86%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionDetailsMapper.kt (100%) rename apps/flipcash/shared/transaction-history/src/{main => commonMain}/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt (95%) create mode 100644 apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.kt create mode 100644 apps/flipcash/shared/transaction-history/src/jvmMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.jvm.kt create mode 100644 apps/flipcash/shared/transaction-history/src/jvmMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.jvm.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt index 945ba5768c..a4ee6b636a 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt +++ b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt @@ -12,6 +12,8 @@ actual object CoreStrings { actual val label_fromDepreciation: Int = R.string.label_fromDepreciation actual val label_fromCurrencyAppreciation: Int = R.string.label_fromCurrencyAppreciation actual val label_fromCurrencyDepreciation: Int = R.string.label_fromCurrencyDepreciation + actual val title_recentActivity: Int = R.string.title_recentActivity + actual val label_chatReceipt_yesterday: Int = R.string.label_chatReceipt_yesterday } @Suppress("PropertyName") diff --git a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt index 61f1fda6ba..7d67f48ab1 100644 --- a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt @@ -15,6 +15,8 @@ expect object CoreStrings { val label_fromDepreciation: Int val label_fromCurrencyAppreciation: Int val label_fromCurrencyDepreciation: Int + val title_recentActivity: Int + val label_chatReceipt_yesterday: Int } @Suppress("PropertyName") diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/updater/NetworkUpdater.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/updater/NetworkUpdater.kt similarity index 100% rename from apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/updater/NetworkUpdater.kt rename to apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/updater/NetworkUpdater.kt diff --git a/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt index 0861caa072..9b386346e5 100644 --- a/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt +++ b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt @@ -11,6 +11,8 @@ import com.flipcash.core.resources.subtitle_addMoney import com.flipcash.core.resources.subtitle_scanTipCard import com.flipcash.core.resources.title_addMoney import com.flipcash.core.resources.title_scanTipCard +import com.flipcash.core.resources.title_recentActivity +import com.flipcash.core.resources.label_chatReceipt_yesterday import com.getcode.util.resources.ComposeResourceIds @Suppress("PropertyName") @@ -23,6 +25,8 @@ actual object CoreStrings { actual val label_fromDepreciation: Int = ComposeResourceIds.register(Res.string.label_fromDepreciation) actual val label_fromCurrencyAppreciation: Int = ComposeResourceIds.register(Res.string.label_fromCurrencyAppreciation) actual val label_fromCurrencyDepreciation: Int = ComposeResourceIds.register(Res.string.label_fromCurrencyDepreciation) + actual val title_recentActivity: Int = ComposeResourceIds.register(Res.string.title_recentActivity) + actual val label_chatReceipt_yesterday: Int = ComposeResourceIds.register(Res.string.label_chatReceipt_yesterday) } @Suppress("PropertyName") diff --git a/apps/flipcash/shared/transaction-history/build.gradle.kts b/apps/flipcash/shared/transaction-history/build.gradle.kts index ee44a7fbf1..2f0ad39f79 100644 --- a/apps/flipcash/shared/transaction-history/build.gradle.kts +++ b/apps/flipcash/shared/transaction-history/build.gradle.kts @@ -1,30 +1,84 @@ -plugins { - alias(libs.plugins.flipcash.android.feature) -} +import com.getcode.buildlogic.resources.androidResAsComposeResources -android { - namespace = "com.flipcash.shared.transactionhistory" +plugins { + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.jetbrains.compose) + alias(libs.plugins.kotlin.ksp) } // Home of the transaction-history data engine (ActivityFeedCoordinator / ActivityFeedUpdater), // consolidated out of the deprecated :shared:activityfeed. Mirrors that module's dependencies. // See docs/superpowers/plans/2026-08-11-activityfeed-phaseout.md. -dependencies { - implementation(libs.bundles.room) - implementation(libs.androidx.paging.runtime) - implementation(libs.compose.paging) +kotlin { + android { + namespace = "com.flipcash.shared.transactionhistory" + androidResources { + enable = true + } + } + + sourceSets { + commonMain.dependencies { + implementation(project(":apps:flipcash:core")) + implementation(project(":apps:flipcash:core-ui")) + implementation(project(":apps:flipcash:shared:persistence:sources")) + implementation(project(":apps:flipcash:shared:common-ui")) + implementation(project(":services:flipcash")) + implementation(project(":services:opencode")) + implementation(project(":libs:datetime")) + implementation(project(":libs:encryption:keys")) + implementation(project(":libs:encryption:utils")) + implementation(project(":libs:logging")) + implementation(project(":ui:core")) + implementation(project(":ui:resources")) + implementation(project(":ui:theme")) - compileOnly(project(":apps:flipcash:shared:persistence:db")) - implementation(project(":apps:flipcash:shared:persistence:sources")) - implementation(project(":apps:flipcash:shared:common-ui")) - implementation(project(":services:flipcash")) - implementation(project(":libs:datetime")) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.material) + implementation(libs.jetbrains.compose.material3) + implementation(libs.jetbrains.compose.components.resources) + implementation(libs.androidx.paging.common) + implementation(libs.javax.inject) + compileOnly(project(":apps:flipcash:shared:persistence:db")) + } + androidMain.dependencies { + // What flipcash.android.feature supplied before the move to KMP. + implementation(libs.bundles.hilt) + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(libs.bundles.compose) + implementation(libs.bundles.haze) + implementation(project(":libs:coroutines")) + implementation(project(":ui:navigation")) + implementation(project(":ui:components")) - testImplementation(libs.bundles.unit.testing) - testImplementation(libs.robolectric) - testImplementation(project(":ui:resources:test-fixtures")) - // Screenshot renders only: FlipcashPreview for the theme, ExchangeStub for the currency flag - // LocalExchange would otherwise resolve to ExchangeNull. - testImplementation(project(":apps:flipcash:shared:theme")) - testImplementation(project(":services:opencode-compose")) + implementation(libs.bundles.room) + implementation(libs.androidx.paging.runtime) + implementation(libs.compose.paging) + } + getByName("androidHostTest").dependencies { + implementation(libs.bundles.unit.testing) + implementation(libs.robolectric) + implementation(project(":ui:resources:test-fixtures")) + // Screenshot renders only: FlipcashPreview for the theme, ExchangeStub for the currency flag + // LocalExchange would otherwise resolve to ExchangeNull. + implementation(project(":apps:flipcash:shared:theme")) + implementation(project(":services:opencode-compose")) + } + } +} + +compose.resources { + packageOfResClass = "com.flipcash.shared.transactionhistory.generated.resources" + customDirectory( + sourceSetName = "commonMain", + directoryProvider = androidResAsComposeResources(), + ) +} + +dependencies { + add("kspAndroid", libs.hilt.android.compiler) + add("kspAndroid", libs.hilt.compiler) } diff --git a/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/MessageDirectionTest.kt b/apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/MessageDirectionTest.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/MessageDirectionTest.kt rename to apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/MessageDirectionTest.kt diff --git a/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/MintInvolvementTest.kt b/apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/MintInvolvementTest.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/MintInvolvementTest.kt rename to apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/MintInvolvementTest.kt diff --git a/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsMapperTest.kt b/apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsMapperTest.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsMapperTest.kt rename to apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsMapperTest.kt diff --git a/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsSamples.kt b/apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsSamples.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsSamples.kt rename to apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsSamples.kt diff --git a/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsScreenshotTest.kt b/apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsScreenshotTest.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsScreenshotTest.kt rename to apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsScreenshotTest.kt diff --git a/apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt b/apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/test/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt rename to apps/flipcash/shared/transaction-history/src/androidHostTest/kotlin/com/flipcash/shared/transactionhistory/TransactionItemMapperTest.kt diff --git a/apps/flipcash/shared/transaction-history/src/test/resources/tokens/dollars.webp b/apps/flipcash/shared/transaction-history/src/androidHostTest/resources/tokens/dollars.webp similarity index 100% rename from apps/flipcash/shared/transaction-history/src/test/resources/tokens/dollars.webp rename to apps/flipcash/shared/transaction-history/src/androidHostTest/resources/tokens/dollars.webp diff --git a/apps/flipcash/shared/transaction-history/src/test/resources/tokens/jeffy.png b/apps/flipcash/shared/transaction-history/src/androidHostTest/resources/tokens/jeffy.png similarity index 100% rename from apps/flipcash/shared/transaction-history/src/test/resources/tokens/jeffy.png rename to apps/flipcash/shared/transaction-history/src/androidHostTest/resources/tokens/jeffy.png diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryScreen.kt b/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryScreen.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryScreen.kt rename to apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryScreen.kt diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryViewModel.kt b/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryViewModel.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryViewModel.kt rename to apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/ActivityHistoryViewModel.kt diff --git a/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.android.kt b/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.android.kt new file mode 100644 index 0000000000..17d56d6ab4 --- /dev/null +++ b/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.android.kt @@ -0,0 +1,8 @@ +package com.flipcash.shared.transactionhistory + +import android.text.format.DateFormat +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +@Composable +internal actual fun is24HourClock(): Boolean = DateFormat.is24HourFormat(LocalContext.current) diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsContent.kt b/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsContent.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsContent.kt rename to apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/TransactionDetailsContent.kt diff --git a/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.android.kt b/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.android.kt new file mode 100644 index 0000000000..3de9123fd1 --- /dev/null +++ b/apps/flipcash/shared/transaction-history/src/androidMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.android.kt @@ -0,0 +1,34 @@ +package com.flipcash.shared.transactionhistory.resources + +import com.flipcash.shared.transactionhistory.R + +@Suppress("PropertyName") +actual object TxStrings { + actual val label_activity_convertFee: Int = R.string.label_activity_convertFee + actual val label_txnDetails_from: Int = R.string.label_txnDetails_from + actual val label_txnDetails_inPerson: Int = R.string.label_txnDetails_inPerson + actual val label_txnDetails_paidWith: Int = R.string.label_txnDetails_paidWith + actual val label_txnDetails_soldFor: Int = R.string.label_txnDetails_soldFor + actual val label_txnDetails_status_completed: Int = R.string.label_txnDetails_status_completed + actual val label_txnDetails_status_failed: Int = R.string.label_txnDetails_status_failed + actual val label_txnDetails_status_pending: Int = R.string.label_txnDetails_status_pending + actual val label_txnDetails_status_unknown: Int = R.string.label_txnDetails_status_unknown + actual val label_txnDetails_to: Int = R.string.label_txnDetails_to + actual val title_activity_convert: Int = R.string.title_activity_convert + actual val title_activity_receivedFrom: Int = R.string.title_activity_receivedFrom + actual val title_activity_sentTo: Int = R.string.title_activity_sentTo + actual val title_activity_tipFrom: Int = R.string.title_activity_tipFrom + actual val title_txnDetails_buy: Int = R.string.title_txnDetails_buy + actual val title_txnDetails_convert: Int = R.string.title_txnDetails_convert + actual val title_txnDetails_deposit: Int = R.string.title_txnDetails_deposit + actual val title_txnDetails_poolPayment: Int = R.string.title_txnDetails_poolPayment + actual val title_txnDetails_sell: Int = R.string.title_txnDetails_sell + actual val title_txnDetails_unknown: Int = R.string.title_txnDetails_unknown + actual val title_txnDetails_withdraw: Int = R.string.title_txnDetails_withdraw + actual val title_txnDetails_youGaveCash: Int = R.string.title_txnDetails_youGaveCash + actual val title_txnDetails_youReceived: Int = R.string.title_txnDetails_youReceived + actual val title_txnDetails_youReceivedCash: Int = R.string.title_txnDetails_youReceivedCash + actual val title_txnDetails_youSent: Int = R.string.title_txnDetails_youSent + actual val title_txnDetails_youSentCashLink: Int = R.string.title_txnDetails_youSentCashLink + actual val title_txnDetails_youTipped: Int = R.string.title_txnDetails_youTipped +} diff --git a/apps/flipcash/shared/transaction-history/src/main/res/values/strings.xml b/apps/flipcash/shared/transaction-history/src/androidMain/res/values/strings.xml similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/res/values/strings.xml rename to apps/flipcash/shared/transaction-history/src/androidMain/res/values/strings.xml diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedCoordinator.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedCoordinator.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedCoordinator.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedCoordinator.kt diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedRow.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedRow.kt similarity index 91% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedRow.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedRow.kt index f5c83a98e1..9cc8076280 100644 --- a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedRow.kt +++ b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedRow.kt @@ -1,6 +1,7 @@ package com.flipcash.shared.transactionhistory -import android.text.format.DateFormat +import com.flipcash.shared.transactionhistory.resources.TxStrings +import com.flipcash.app.core.resources.CoreStrings import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -11,8 +12,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource +import com.getcode.util.resources.stringRes import androidx.compose.ui.text.style.TextOverflow import com.flipcash.app.core.ui.ActivityAmount import com.getcode.theme.CodeTheme @@ -82,7 +82,7 @@ fun ActivityFeedRow( maxLines = 1, ) Text( - text = stringResource(R.string.label_activity_convertFee, fee.formatted()), + text = stringRes(TxStrings.label_activity_convertFee, fee.formatted()), style = CodeTheme.typography.textSmall, color = CodeTheme.colors.textSecondary, maxLines = 1, @@ -106,8 +106,7 @@ fun ActivityFeedRow( */ @Composable internal fun formatActivityTimestamp(instant: Instant): String { - val context = LocalContext.current - val is24Hour = DateFormat.is24HourFormat(context) + val is24Hour = is24HourClock() val tz = TimeZone.currentSystemDefault() val todayDate = Clock.System.now().toLocalDateTime(tz).date val messageDate = instant.toLocalDateTime(tz).date @@ -115,7 +114,7 @@ internal fun formatActivityTimestamp(instant: Instant): String { val time = instant.formatLocalized("h:mm a", is24Hour = is24Hour, if24Hour = "H:mm") return when { dayDiff == 0L -> time - dayDiff == 1L -> stringResource(R.string.label_chatReceipt_yesterday) + dayDiff == 1L -> stringRes(CoreStrings.label_chatReceipt_yesterday) dayDiff in 2L..6L -> instant.formatLocalized("EEEE") messageDate.year == todayDate.year -> instant.formatLocalized("MMM d") else -> instant.formatLocalized("MMM d, yyyy") diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedUpdater.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedUpdater.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedUpdater.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ActivityFeedUpdater.kt diff --git a/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.kt new file mode 100644 index 0000000000..c2e2ff9cba --- /dev/null +++ b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.kt @@ -0,0 +1,7 @@ +package com.flipcash.shared.transactionhistory + +import androidx.compose.runtime.Composable + +/** Whether the user's clock setting is 24-hour. */ +@Composable +internal expect fun is24HourClock(): Boolean diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/MintInvolvement.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/MintInvolvement.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/MintInvolvement.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/MintInvolvement.kt diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/RecentActivitySection.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/RecentActivitySection.kt similarity index 89% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/RecentActivitySection.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/RecentActivitySection.kt index 05f3147efb..bc0c95e73a 100644 --- a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/RecentActivitySection.kt +++ b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/RecentActivitySection.kt @@ -1,5 +1,7 @@ package com.flipcash.shared.transactionhistory +import com.flipcash.app.core.resources.CoreStrings +import com.flipcash.app.core.resources.CoreDrawables import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -14,10 +16,9 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource +import com.getcode.util.resources.painterForResId +import com.getcode.util.resources.stringRes import com.getcode.theme.CodeTheme -import com.getcode.util.resources.R /** * The "Recent" activity section — a tappable header (label + trailing chevron) followed by the list of @@ -63,13 +64,13 @@ private fun LazyItemScope.RecentActivityHeader(modifier: Modifier) { verticalAlignment = Alignment.CenterVertically, ) { Text( - text = stringResource(com.flipcash.core.R.string.title_recentActivity), + text = stringRes(CoreStrings.title_recentActivity), style = CodeTheme.typography.screenTitle, color = CodeTheme.colors.textMain, ) Icon( modifier = Modifier.size(CodeTheme.dimens.staticGrid.x3), - painter = painterResource(R.drawable.ic_chevron_right), + painter = painterForResId(CoreDrawables.ic_chevron_right), contentDescription = null, tint = CodeTheme.colors.textSecondary, ) diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ResolvedTransaction.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ResolvedTransaction.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/ResolvedTransaction.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/ResolvedTransaction.kt diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionAvatarImage.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionAvatarImage.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionAvatarImage.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionAvatarImage.kt diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionDetails.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionDetails.kt similarity index 84% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionDetails.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionDetails.kt index 13d60933ca..2b14ba1ac8 100644 --- a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionDetails.kt +++ b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionDetails.kt @@ -1,5 +1,6 @@ package com.flipcash.shared.transactionhistory +import com.flipcash.shared.transactionhistory.resources.TxStrings import androidx.annotation.StringRes import com.getcode.opencode.model.financial.Fiat import com.getcode.opencode.model.financial.Token @@ -22,19 +23,19 @@ import kotlin.time.Instant * from taking a bill in person — both arrive as an unattributed `ReceivedCrypto`. */ enum class TransactionKind(@StringRes val headingRes: Int) { - Tipped(R.string.title_txnDetails_youTipped), - Received(R.string.title_txnDetails_youReceived), - Sent(R.string.title_txnDetails_youSent), - GaveCash(R.string.title_txnDetails_youGaveCash), - ReceivedCash(R.string.title_txnDetails_youReceivedCash), - SentCashLink(R.string.title_txnDetails_youSentCashLink), - Buy(R.string.title_txnDetails_buy), - Sell(R.string.title_txnDetails_sell), - Withdraw(R.string.title_txnDetails_withdraw), - Deposit(R.string.title_txnDetails_deposit), - Convert(R.string.title_txnDetails_convert), - PoolPayment(R.string.title_txnDetails_poolPayment), - Unknown(R.string.title_txnDetails_unknown), + Tipped(TxStrings.title_txnDetails_youTipped), + Received(TxStrings.title_txnDetails_youReceived), + Sent(TxStrings.title_txnDetails_youSent), + GaveCash(TxStrings.title_txnDetails_youGaveCash), + ReceivedCash(TxStrings.title_txnDetails_youReceivedCash), + SentCashLink(TxStrings.title_txnDetails_youSentCashLink), + Buy(TxStrings.title_txnDetails_buy), + Sell(TxStrings.title_txnDetails_sell), + Withdraw(TxStrings.title_txnDetails_withdraw), + Deposit(TxStrings.title_txnDetails_deposit), + Convert(TxStrings.title_txnDetails_convert), + PoolPayment(TxStrings.title_txnDetails_poolPayment), + Unknown(TxStrings.title_txnDetails_unknown), } /** @@ -49,8 +50,8 @@ data class TransactionAccount( val direction: Direction, ) { enum class Direction(@StringRes val labelRes: Int) { - To(R.string.label_txnDetails_to), - From(R.string.label_txnDetails_from), + To(TxStrings.label_txnDetails_to), + From(TxStrings.label_txnDetails_from), } /** Nobody reads the middle of an address; the ends are what someone actually compares. */ @@ -65,10 +66,10 @@ data class TransactionAccount( /** The entry's settlement state, as the Status row phrases it. */ enum class TransactionStatus(@StringRes val labelRes: Int) { - Pending(R.string.label_txnDetails_status_pending), - Completed(R.string.label_txnDetails_status_completed), - Failed(R.string.label_txnDetails_status_failed), - Unknown(R.string.label_txnDetails_status_unknown), + Pending(TxStrings.label_txnDetails_status_pending), + Completed(TxStrings.label_txnDetails_status_completed), + Failed(TxStrings.label_txnDetails_status_failed), + Unknown(TxStrings.label_txnDetails_status_unknown), } /** diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionListItem.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionListItem.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionListItem.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionListItem.kt diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionSubtitles.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionSubtitles.kt similarity index 86% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionSubtitles.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionSubtitles.kt index 31649136c1..0d30391d3d 100644 --- a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/TransactionSubtitles.kt +++ b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/TransactionSubtitles.kt @@ -1,5 +1,6 @@ package com.flipcash.shared.transactionhistory +import com.flipcash.shared.transactionhistory.resources.TxStrings import com.getcode.util.resources.ResourceHelper /** @@ -27,22 +28,22 @@ object TransactionSubtitles { * exchanges identities, so "who" is genuinely unanswerable; how it moved is the useful fact. */ fun inPerson(resources: ResourceHelper): String = - resources.getString(R.string.label_txnDetails_inPerson) + resources.getString(TxStrings.label_txnDetails_inPerson) /** [TransactionKind.Buy] — the mint that was spent, which the amount alone never says. */ fun paidWith(resources: ResourceHelper, tokenName: String): String = - resources.getString(R.string.label_txnDetails_paidWith, tokenName) + resources.getString(TxStrings.label_txnDetails_paidWith, tokenName) /** [TransactionKind.Sell] — the mint that came back. */ fun soldFor(resources: ResourceHelper, tokenName: String): String = - resources.getString(R.string.label_txnDetails_soldFor, tokenName) + resources.getString(TxStrings.label_txnDetails_soldFor, tokenName) /** * [TransactionKind.Convert] — both mints, in the same "from → to" shape the activity row's * convert title already uses, so the row and the screen it opens read alike. */ fun converted(resources: ResourceHelper, from: String, to: String): String = - resources.getString(R.string.title_activity_convert, from, to) + resources.getString(TxStrings.title_activity_convert, from, to) /** [TransactionKind.PoolPayment] — the pool that was paid. */ fun pool(name: String): String? = name.takeIf { it.isNotBlank() } diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionDetailsMapper.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionDetailsMapper.kt similarity index 100% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionDetailsMapper.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionDetailsMapper.kt diff --git a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt similarity index 95% rename from apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt rename to apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt index 8009e0d328..7004d06418 100644 --- a/apps/flipcash/shared/transaction-history/src/main/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt +++ b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/internal/TransactionItemMapper.kt @@ -1,10 +1,10 @@ package com.flipcash.shared.transactionhistory.internal +import com.flipcash.shared.transactionhistory.resources.TxStrings import com.flipcash.app.core.feed.ActivityFeedMessageWithToken import com.flipcash.app.core.feed.MessageMetadata import com.flipcash.app.core.feed.MessageSubstitution import com.flipcash.services.models.UserProfile -import com.flipcash.shared.transactionhistory.R import com.flipcash.shared.transactionhistory.TransactionAvatar import com.flipcash.shared.transactionhistory.TransactionListItem import com.flipcash.shared.transactionhistory.convertOf @@ -82,7 +82,7 @@ private fun convertTitle( convertOf(source.message.metadata) ?: return null val from = source.token?.name?.takeIf { it.isNotBlank() } ?: return null val to = source.toToken?.name?.takeIf { it.isNotBlank() } ?: return null - return resources.getString(R.string.title_activity_convert, from, to) + return resources.getString(TxStrings.title_activity_convert, from, to) } /** @@ -130,12 +130,12 @@ private fun resolveTitle( val isTip = text.isTipVerb() return when (meta) { is MessageMetadata.ReceivedCrypto -> - if (isTip) resources.getString(R.string.title_activity_tipFrom, counterpartyName) - else resources.getString(R.string.title_activity_receivedFrom, counterpartyName) + if (isTip) resources.getString(TxStrings.title_activity_tipFrom, counterpartyName) + else resources.getString(TxStrings.title_activity_receivedFrom, counterpartyName) is MessageMetadata.DirectlySentCrypto -> // "Tipped" already reads as a transitive verb; "Sent" needs the preposition. if (isTip) "$text $counterpartyName" - else resources.getString(R.string.title_activity_sentTo, counterpartyName) + else resources.getString(TxStrings.title_activity_sentTo, counterpartyName) else -> "$text $counterpartyName" } } diff --git a/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.kt b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.kt new file mode 100644 index 0000000000..d707ec3968 --- /dev/null +++ b/apps/flipcash/shared/transaction-history/src/commonMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.kt @@ -0,0 +1,37 @@ +package com.flipcash.shared.transactionhistory.resources + +/** + * `Int` IDs for the transaction-history strings that common code hands to + * [com.getcode.util.resources.ResourceHelper] and [com.getcode.util.resources.stringRes]. Android + * returns the `R` IDs; desktop registers the generated Compose resources. + */ +@Suppress("PropertyName") +expect object TxStrings { + val label_activity_convertFee: Int + val label_txnDetails_from: Int + val label_txnDetails_inPerson: Int + val label_txnDetails_paidWith: Int + val label_txnDetails_soldFor: Int + val label_txnDetails_status_completed: Int + val label_txnDetails_status_failed: Int + val label_txnDetails_status_pending: Int + val label_txnDetails_status_unknown: Int + val label_txnDetails_to: Int + val title_activity_convert: Int + val title_activity_receivedFrom: Int + val title_activity_sentTo: Int + val title_activity_tipFrom: Int + val title_txnDetails_buy: Int + val title_txnDetails_convert: Int + val title_txnDetails_deposit: Int + val title_txnDetails_poolPayment: Int + val title_txnDetails_sell: Int + val title_txnDetails_unknown: Int + val title_txnDetails_withdraw: Int + val title_txnDetails_youGaveCash: Int + val title_txnDetails_youReceived: Int + val title_txnDetails_youReceivedCash: Int + val title_txnDetails_youSent: Int + val title_txnDetails_youSentCashLink: Int + val title_txnDetails_youTipped: Int +} diff --git a/apps/flipcash/shared/transaction-history/src/jvmMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.jvm.kt b/apps/flipcash/shared/transaction-history/src/jvmMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.jvm.kt new file mode 100644 index 0000000000..e2df1d8ec2 --- /dev/null +++ b/apps/flipcash/shared/transaction-history/src/jvmMain/kotlin/com/flipcash/shared/transactionhistory/Is24HourClock.jvm.kt @@ -0,0 +1,13 @@ +package com.flipcash.shared.transactionhistory + +import androidx.compose.runtime.Composable +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Locale + +/** Desktop has no clock setting to read, so this follows the locale's short time pattern. */ +@Composable +internal actual fun is24HourClock(): Boolean { + val format = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()) + return (format as? SimpleDateFormat)?.toPattern()?.contains('H') ?: false +} diff --git a/apps/flipcash/shared/transaction-history/src/jvmMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.jvm.kt b/apps/flipcash/shared/transaction-history/src/jvmMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.jvm.kt new file mode 100644 index 0000000000..c04328f65f --- /dev/null +++ b/apps/flipcash/shared/transaction-history/src/jvmMain/kotlin/com/flipcash/shared/transactionhistory/resources/TxResources.jvm.kt @@ -0,0 +1,62 @@ +package com.flipcash.shared.transactionhistory.resources + +import com.flipcash.shared.transactionhistory.generated.resources.Res +import com.flipcash.shared.transactionhistory.generated.resources.label_activity_convertFee +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_from +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_inPerson +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_paidWith +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_soldFor +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_status_completed +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_status_failed +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_status_pending +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_status_unknown +import com.flipcash.shared.transactionhistory.generated.resources.label_txnDetails_to +import com.flipcash.shared.transactionhistory.generated.resources.title_activity_convert +import com.flipcash.shared.transactionhistory.generated.resources.title_activity_receivedFrom +import com.flipcash.shared.transactionhistory.generated.resources.title_activity_sentTo +import com.flipcash.shared.transactionhistory.generated.resources.title_activity_tipFrom +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_buy +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_convert +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_deposit +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_poolPayment +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_sell +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_unknown +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_withdraw +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_youGaveCash +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_youReceived +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_youReceivedCash +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_youSent +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_youSentCashLink +import com.flipcash.shared.transactionhistory.generated.resources.title_txnDetails_youTipped +import com.getcode.util.resources.ComposeResourceIds + +@Suppress("PropertyName") +actual object TxStrings { + actual val label_activity_convertFee: Int = ComposeResourceIds.register(Res.string.label_activity_convertFee) + actual val label_txnDetails_from: Int = ComposeResourceIds.register(Res.string.label_txnDetails_from) + actual val label_txnDetails_inPerson: Int = ComposeResourceIds.register(Res.string.label_txnDetails_inPerson) + actual val label_txnDetails_paidWith: Int = ComposeResourceIds.register(Res.string.label_txnDetails_paidWith) + actual val label_txnDetails_soldFor: Int = ComposeResourceIds.register(Res.string.label_txnDetails_soldFor) + actual val label_txnDetails_status_completed: Int = ComposeResourceIds.register(Res.string.label_txnDetails_status_completed) + actual val label_txnDetails_status_failed: Int = ComposeResourceIds.register(Res.string.label_txnDetails_status_failed) + actual val label_txnDetails_status_pending: Int = ComposeResourceIds.register(Res.string.label_txnDetails_status_pending) + actual val label_txnDetails_status_unknown: Int = ComposeResourceIds.register(Res.string.label_txnDetails_status_unknown) + actual val label_txnDetails_to: Int = ComposeResourceIds.register(Res.string.label_txnDetails_to) + actual val title_activity_convert: Int = ComposeResourceIds.register(Res.string.title_activity_convert) + actual val title_activity_receivedFrom: Int = ComposeResourceIds.register(Res.string.title_activity_receivedFrom) + actual val title_activity_sentTo: Int = ComposeResourceIds.register(Res.string.title_activity_sentTo) + actual val title_activity_tipFrom: Int = ComposeResourceIds.register(Res.string.title_activity_tipFrom) + actual val title_txnDetails_buy: Int = ComposeResourceIds.register(Res.string.title_txnDetails_buy) + actual val title_txnDetails_convert: Int = ComposeResourceIds.register(Res.string.title_txnDetails_convert) + actual val title_txnDetails_deposit: Int = ComposeResourceIds.register(Res.string.title_txnDetails_deposit) + actual val title_txnDetails_poolPayment: Int = ComposeResourceIds.register(Res.string.title_txnDetails_poolPayment) + actual val title_txnDetails_sell: Int = ComposeResourceIds.register(Res.string.title_txnDetails_sell) + actual val title_txnDetails_unknown: Int = ComposeResourceIds.register(Res.string.title_txnDetails_unknown) + actual val title_txnDetails_withdraw: Int = ComposeResourceIds.register(Res.string.title_txnDetails_withdraw) + actual val title_txnDetails_youGaveCash: Int = ComposeResourceIds.register(Res.string.title_txnDetails_youGaveCash) + actual val title_txnDetails_youReceived: Int = ComposeResourceIds.register(Res.string.title_txnDetails_youReceived) + actual val title_txnDetails_youReceivedCash: Int = ComposeResourceIds.register(Res.string.title_txnDetails_youReceivedCash) + actual val title_txnDetails_youSent: Int = ComposeResourceIds.register(Res.string.title_txnDetails_youSent) + actual val title_txnDetails_youSentCashLink: Int = ComposeResourceIds.register(Res.string.title_txnDetails_youSentCashLink) + actual val title_txnDetails_youTipped: Int = ComposeResourceIds.register(Res.string.title_txnDetails_youTipped) +} From 0db5af16c3dfb0feb5c0e178c4a26e3d441d62f5 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 18:59:46 -0400 Subject: [PATCH 11/17] build(funding): lift PurchaseMethod types to common PurchaseMethod, PurchaseMethodController and PurchaseMethodState move to commonMain so the balance view model can take the controller on desktop. The controller implementation and its UI stay in androidMain. The module moves to flipcash.kmp.app.library, since its app-module dependencies have no Apple targets. --- apps/flipcash/shared/funding/build.gradle.kts | 9 +++++++-- .../kotlin/com/flipcash/app/funding/PurchaseMethod.kt | 0 .../com/flipcash/app/funding/PurchaseMethodController.kt | 0 .../com/flipcash/app/funding/PurchaseMethodState.kt | 0 4 files changed, 7 insertions(+), 2 deletions(-) rename apps/flipcash/shared/funding/src/{androidMain => commonMain}/kotlin/com/flipcash/app/funding/PurchaseMethod.kt (100%) rename apps/flipcash/shared/funding/src/{androidMain => commonMain}/kotlin/com/flipcash/app/funding/PurchaseMethodController.kt (100%) rename apps/flipcash/shared/funding/src/{androidMain => commonMain}/kotlin/com/flipcash/app/funding/PurchaseMethodState.kt (100%) diff --git a/apps/flipcash/shared/funding/build.gradle.kts b/apps/flipcash/shared/funding/build.gradle.kts index 03fc03ffca..871af57bf3 100644 --- a/apps/flipcash/shared/funding/build.gradle.kts +++ b/apps/flipcash/shared/funding/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - alias(libs.plugins.flipcash.kmp.library) + alias(libs.plugins.flipcash.kmp.app.library) alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.ksp) } @@ -7,10 +7,15 @@ plugins { kotlin { android { namespace = "${Gradle.flipcashNamespace}.shared.funding" - withHostTest {} androidResources { enable = true } } sourceSets { + commonMain.dependencies { + implementation(project(":apps:flipcash:core")) + implementation(project(":services:flipcash")) + implementation(project(":services:opencode")) + implementation(project(":libs:encryption:keys")) + } androidMain.dependencies { implementation(project.dependencies.platform(libs.compose.bom)) implementation(libs.bundles.compose) diff --git a/apps/flipcash/shared/funding/src/androidMain/kotlin/com/flipcash/app/funding/PurchaseMethod.kt b/apps/flipcash/shared/funding/src/commonMain/kotlin/com/flipcash/app/funding/PurchaseMethod.kt similarity index 100% rename from apps/flipcash/shared/funding/src/androidMain/kotlin/com/flipcash/app/funding/PurchaseMethod.kt rename to apps/flipcash/shared/funding/src/commonMain/kotlin/com/flipcash/app/funding/PurchaseMethod.kt diff --git a/apps/flipcash/shared/funding/src/androidMain/kotlin/com/flipcash/app/funding/PurchaseMethodController.kt b/apps/flipcash/shared/funding/src/commonMain/kotlin/com/flipcash/app/funding/PurchaseMethodController.kt similarity index 100% rename from apps/flipcash/shared/funding/src/androidMain/kotlin/com/flipcash/app/funding/PurchaseMethodController.kt rename to apps/flipcash/shared/funding/src/commonMain/kotlin/com/flipcash/app/funding/PurchaseMethodController.kt diff --git a/apps/flipcash/shared/funding/src/androidMain/kotlin/com/flipcash/app/funding/PurchaseMethodState.kt b/apps/flipcash/shared/funding/src/commonMain/kotlin/com/flipcash/app/funding/PurchaseMethodState.kt similarity index 100% rename from apps/flipcash/shared/funding/src/androidMain/kotlin/com/flipcash/app/funding/PurchaseMethodState.kt rename to apps/flipcash/shared/funding/src/commonMain/kotlin/com/flipcash/app/funding/PurchaseMethodState.kt From 19e03d490e2a6537c6078e98bc94a68a707a0bb8 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 19:05:08 -0400 Subject: [PATCH 12/17] build(tokens): move shared:tokens to android+jvm KMP and lift the wallet coordinators TokenCoordinator, WalletRevealCoordinator, TokenSelectionResolver and SelectTokenViewModel move to commonMain; the rest of the module stays in androidMain. Two app:core seams replace the Android types TokenCoordinator used: - PreferencesFiles hands out the DataStore file; Android binds it to Context.preferencesDataStoreFile through Hilt. - AppProcessLifecycle is ProcessLifecycleOwner on Android and a lifecycle held in RESUMED on desktop, since lifecycle-process has no desktop artifact. SelectTokenViewModel becomes an open class without Hilt annotations. HiltSelectTokenViewModel in androidMain carries @HiltViewModel, and the ten hiltViewModel() call sites across eight features now request it. --- apps/flipcash/core/build.gradle.kts | 2 + .../lifecycle/AppProcessLifecycle.android.kt | 18 ++++ .../core/storage/ContextPreferencesFiles.kt | 25 +++++ .../app/core/lifecycle/AppProcessLifecycle.kt | 18 ++++ .../app/core/storage/PreferencesFiles.kt | 9 ++ .../core/lifecycle/AppProcessLifecycle.jvm.kt | 22 +++++ .../core/storage/DirectoryPreferencesFiles.kt | 8 ++ .../com/flipcash/app/balance/WalletScreen.kt | 3 +- .../screens/FundingSourceSelectionScreen.kt | 3 +- .../flipcash/app/deposit/DepositFlowScreen.kt | 3 +- .../boost/ReactionBoostCurrencySheet.kt | 3 +- .../screens/SelectGroupCurrencySheet.kt | 3 +- .../com/flipcash/app/tokens/SwapFlowScreen.kt | 7 +- .../flipcash/app/tokens/TokenSelectScreen.kt | 3 +- .../app/withdrawal/WithdrawalFlowScreen.kt | 3 +- apps/flipcash/shared/tokens/build.gradle.kts | 92 +++++++++++++------ .../flipcash/app/tokens/BalancePollerTest.kt | 0 .../app/tokens/FeeAffordableEntryTest.kt | 0 .../TokenCoordinatorMintOverloadTest.kt | 0 .../app/tokens/TokenSelectionResolverTest.kt | 0 .../app/tokens/TokenStateRepricingTest.kt | 0 .../TransactionNotificationPromptTest.kt | 0 .../app/tokens/UsdcDepositSweepTest.kt | 0 .../app/tokens/WalletRevealCoordinatorTest.kt | 0 .../BondingCurveProjectionTest.kt | 0 .../app/tokens/data/AggregationTypeTest.kt | 0 .../app/tokens/ui/DollarsConversionFeeTest.kt | 0 .../ui/MarketCapExplainerViewModelTest.kt | 0 .../ui/SelectTokenViewModelStateTest.kt | 0 .../ui/SwapViewModelBalancePrefetchTest.kt | 0 .../app/tokens/ui/SwapViewModelErrorTest.kt | 0 .../app/tokens/ui/SwapViewModelStateTest.kt | 0 .../ui/TokenInfoViewModelChartLoadTest.kt | 0 .../ui/TokenInfoViewModelConvertTest.kt | 0 .../tokens/ui/TokenInfoViewModelStateTest.kt | 0 .../com/flipcash/app/tokens/BalancePoller.kt | 0 .../flipcash/app/tokens/FeeAffordableEntry.kt | 0 .../flipcash/app/tokens/InlineCompletion.kt | 0 .../com/flipcash/app/tokens/TokenUpdater.kt | 0 .../tokens/TransactionNotificationPrompt.kt | 0 .../flipcash/app/tokens/UsdcDepositSweep.kt | 0 .../bondingcurve/BondingCurveProjection.kt | 0 .../app/tokens/data/AggregationType.kt | 0 .../app/tokens/data/MarketCapPoint.kt | 0 .../flipcash/app/tokens/data/MarketTrend.kt | 0 .../com/flipcash/app/tokens/data/Period.kt | 0 .../app/tokens/inject/PurchaseMethodModule.kt | 0 .../flipcash/app/tokens/inject/TokenModule.kt | 0 .../app/tokens/ui/HiltSelectTokenViewModel.kt | 15 +++ .../tokens/ui/MarketCapExplainerViewModel.kt | 0 .../flipcash/app/tokens/ui/SwapViewModel.kt | 0 .../app/tokens/ui/TokenInfoViewModel.kt | 0 .../com/flipcash/app/tokens/ui/TokenList.kt | 0 .../flipcash/app/tokens/TokenCoordinator.kt | 18 ++-- .../app/tokens/TokenSelectionResolver.kt | 0 .../app/tokens/WalletRevealCoordinator.kt | 0 .../app/tokens/ui/SelectTokenViewModel.kt | 8 +- 57 files changed, 210 insertions(+), 53 deletions(-) create mode 100644 apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.android.kt create mode 100644 apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/storage/ContextPreferencesFiles.kt create mode 100644 apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.kt create mode 100644 apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/storage/PreferencesFiles.kt create mode 100644 apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.jvm.kt create mode 100644 apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/storage/DirectoryPreferencesFiles.kt rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/BalancePollerTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/FeeAffordableEntryTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/TokenCoordinatorMintOverloadTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/TokenSelectionResolverTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/TokenStateRepricingTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/TransactionNotificationPromptTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/UsdcDepositSweepTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/WalletRevealCoordinatorTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjectionTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/data/AggregationTypeTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/DollarsConversionFeeTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModelTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModelStateTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/SwapViewModelBalancePrefetchTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/SwapViewModelErrorTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelChartLoadTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelConvertTest.kt (100%) rename apps/flipcash/shared/tokens/src/{test => androidHostTest}/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelStateTest.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/BalancePoller.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/FeeAffordableEntry.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/InlineCompletion.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/TokenUpdater.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/TransactionNotificationPrompt.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjection.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/data/AggregationType.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/data/MarketCapPoint.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/data/MarketTrend.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/data/Period.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/inject/PurchaseMethodModule.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/inject/TokenModule.kt (100%) create mode 100644 apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/HiltSelectTokenViewModel.kt rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModel.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt (100%) rename apps/flipcash/shared/tokens/src/{main => androidMain}/kotlin/com/flipcash/app/tokens/ui/TokenList.kt (100%) rename apps/flipcash/shared/tokens/src/{main => commonMain}/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt (98%) rename apps/flipcash/shared/tokens/src/{main => commonMain}/kotlin/com/flipcash/app/tokens/TokenSelectionResolver.kt (100%) rename apps/flipcash/shared/tokens/src/{main => commonMain}/kotlin/com/flipcash/app/tokens/WalletRevealCoordinator.kt (100%) rename apps/flipcash/shared/tokens/src/{main => commonMain}/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt (97%) diff --git a/apps/flipcash/core/build.gradle.kts b/apps/flipcash/core/build.gradle.kts index d28cbdf678..d7da03a813 100644 --- a/apps/flipcash/core/build.gradle.kts +++ b/apps/flipcash/core/build.gradle.kts @@ -19,6 +19,7 @@ kotlin { commonMain.dependencies { implementation(project(":libs:parcelable")) implementation(libs.javax.inject) + api(libs.androidx.lifecycle.runtime.common) api(project(":ui:navigation")) api(project(":ui:core")) api(libs.navigation3.runtime) @@ -54,6 +55,7 @@ kotlin { implementation(libs.firebase.messaging) implementation(libs.androidx.localbroadcastmanager) implementation(libs.androidx.datastore) + implementation(libs.androidx.lifecycle.process) implementation(libs.compose.material3) api(libs.coil3) api(libs.coil3.network) diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.android.kt b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.android.kt new file mode 100644 index 0000000000..adecd9b283 --- /dev/null +++ b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.android.kt @@ -0,0 +1,18 @@ +package com.flipcash.app.core.lifecycle + +import android.os.Handler +import android.os.Looper +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner + +actual object AppProcessLifecycle { + actual val owner: LifecycleOwner + get() = ProcessLifecycleOwner.get() + + actual fun addObserver(observer: LifecycleObserver) { + Handler(Looper.getMainLooper()).post { + owner.lifecycle.addObserver(observer) + } + } +} diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/storage/ContextPreferencesFiles.kt b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/storage/ContextPreferencesFiles.kt new file mode 100644 index 0000000000..26e32fdb13 --- /dev/null +++ b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/storage/ContextPreferencesFiles.kt @@ -0,0 +1,25 @@ +package com.flipcash.app.core.storage + +import android.content.Context +import androidx.datastore.preferences.preferencesDataStoreFile +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import java.io.File +import javax.inject.Inject + +/** [PreferencesFiles] backed by [preferencesDataStoreFile], the location DataStore has always used. */ +internal class ContextPreferencesFiles @Inject constructor( + @param:ApplicationContext private val context: Context, +) : PreferencesFiles { + override fun file(name: String): File = context.preferencesDataStoreFile(name) +} + +@Module +@InstallIn(SingletonComponent::class) +internal abstract class PreferencesFilesModule { + @Binds + abstract fun bindPreferencesFiles(impl: ContextPreferencesFiles): PreferencesFiles +} diff --git a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.kt new file mode 100644 index 0000000000..1b6872a281 --- /dev/null +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.kt @@ -0,0 +1,18 @@ +package com.flipcash.app.core.lifecycle + +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner + +/** + * The process-wide lifecycle. On Android it is [androidx.lifecycle.ProcessLifecycleOwner]; on + * desktop, where `lifecycle-process` has no artifact, it is RESUMED for as long as the app runs. + */ +expect object AppProcessLifecycle { + val owner: LifecycleOwner + + /** + * Adds [observer] to [owner]'s lifecycle. Safe to call from any thread: on Android the add is + * posted to the main thread, which `addObserver` requires. + */ + fun addObserver(observer: LifecycleObserver) +} diff --git a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/storage/PreferencesFiles.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/storage/PreferencesFiles.kt new file mode 100644 index 0000000000..760a6d0849 --- /dev/null +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/storage/PreferencesFiles.kt @@ -0,0 +1,9 @@ +package com.flipcash.app.core.storage + +import java.io.File + +/** Where DataStore preference files live: the app's files dir on Android, an app data dir on desktop. */ +fun interface PreferencesFiles { + /** The file backing the preferences store called [name]. */ + fun file(name: String): File +} diff --git a/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.jvm.kt b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.jvm.kt new file mode 100644 index 0000000000..1170ace0e9 --- /dev/null +++ b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/lifecycle/AppProcessLifecycle.jvm.kt @@ -0,0 +1,22 @@ +package com.flipcash.app.core.lifecycle + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry + +actual object AppProcessLifecycle { + actual val owner: LifecycleOwner = object : LifecycleOwner { + override val lifecycle: Lifecycle get() = registry + } + + // A desktop window has no background state the app needs to react to yet, so the process + // stays RESUMED. Window minimise/close can drive this registry once something needs it. + private val registry = LifecycleRegistry.createUnsafe(owner).apply { + currentState = Lifecycle.State.RESUMED + } + + actual fun addObserver(observer: LifecycleObserver) { + synchronized(registry) { registry.addObserver(observer) } + } +} diff --git a/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/storage/DirectoryPreferencesFiles.kt b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/storage/DirectoryPreferencesFiles.kt new file mode 100644 index 0000000000..5a698a06d4 --- /dev/null +++ b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/storage/DirectoryPreferencesFiles.kt @@ -0,0 +1,8 @@ +package com.flipcash.app.core.storage + +import java.io.File + +/** [PreferencesFiles] rooted at [directory], using DataStore's `.preferences_pb` naming. */ +class DirectoryPreferencesFiles(private val directory: File) : PreferencesFiles { + override fun file(name: String): File = File(directory, "datastore/$name.preferences_pb") +} diff --git a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/WalletScreen.kt b/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/WalletScreen.kt index 5bfd043f03..09874e18c5 100644 --- a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/WalletScreen.kt +++ b/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/WalletScreen.kt @@ -13,6 +13,7 @@ import com.flipcash.app.core.AppRoute import com.flipcash.app.core.extensions.openAsSheet import com.flipcash.app.core.tokens.TokenPurpose import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.flipcash.app.tokens.ui.HiltSelectTokenViewModel import com.getcode.navigation.core.LocalCodeNavigator import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn @@ -27,7 +28,7 @@ fun WalletScreen() { horizontalAlignment = Alignment.CenterHorizontally, ) { val viewModel = hiltViewModel() - val tokenViewModel = hiltViewModel() + val tokenViewModel = hiltViewModel() WalletScreen(viewModel, tokenViewModel) LaunchedEffect(tokenViewModel) { diff --git a/apps/flipcash/features/currency-creator/src/main/kotlin/com/flipcash/app/currencycreator/internal/screens/FundingSourceSelectionScreen.kt b/apps/flipcash/features/currency-creator/src/main/kotlin/com/flipcash/app/currencycreator/internal/screens/FundingSourceSelectionScreen.kt index 0299395bad..01063ee26e 100644 --- a/apps/flipcash/features/currency-creator/src/main/kotlin/com/flipcash/app/currencycreator/internal/screens/FundingSourceSelectionScreen.kt +++ b/apps/flipcash/features/currency-creator/src/main/kotlin/com/flipcash/app/currencycreator/internal/screens/FundingSourceSelectionScreen.kt @@ -12,6 +12,7 @@ import com.flipcash.app.core.tokens.TokenPurpose import com.flipcash.app.currencycreator.internal.CurrencyCreatorViewModel import com.flipcash.app.tokens.TokenSelectScreen import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.flipcash.app.tokens.ui.HiltSelectTokenViewModel import com.getcode.navigation.flow.flowSharedViewModel import com.getcode.navigation.flow.rememberFlowNavigator import kotlinx.coroutines.flow.filter @@ -22,7 +23,7 @@ import kotlinx.coroutines.flow.onEach @Composable internal fun FundingSourceSelectionScreen() { - val selectionViewModel = hiltViewModel() + val selectionViewModel = hiltViewModel() val viewModel = flowSharedViewModel() val state by viewModel.stateFlow.collectAsStateWithLifecycle() val flowNavigator = rememberFlowNavigator() diff --git a/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt b/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt index dd12134da0..1261aa7524 100644 --- a/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt +++ b/apps/flipcash/features/deposit/src/main/kotlin/com/flipcash/app/deposit/DepositFlowScreen.kt @@ -24,6 +24,7 @@ import com.flipcash.app.deposit.internal.DepositViewModel import com.flipcash.app.deposit.internal.UsdcDepositInformationScreen import com.flipcash.app.theme.FlipcashThemeWrapper import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.flipcash.app.tokens.ui.HiltSelectTokenViewModel import com.flipcash.app.tokens.ui.TokenList import com.flipcash.core.R import com.getcode.navigation.annotatedEntry @@ -99,7 +100,7 @@ private fun depositEntryProvider( @Composable private fun DepositSelectTokenScreen() { val flowNavigator = rememberFlowNavigator() - val viewModel = hiltViewModel() + val viewModel = hiltViewModel() val state by viewModel.stateFlow.collectAsStateWithLifecycle() Column( diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/boost/ReactionBoostCurrencySheet.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/boost/ReactionBoostCurrencySheet.kt index f6e9e49145..11799e90c3 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/boost/ReactionBoostCurrencySheet.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/boost/ReactionBoostCurrencySheet.kt @@ -20,6 +20,7 @@ import com.flipcash.app.core.ui.TokenBalanceStyle import com.flipcash.app.core.ui.TokenSelectionStyle import com.flipcash.app.core.ui.rememberTokenBalanceRowStyling import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.flipcash.app.tokens.ui.HiltSelectTokenViewModel import com.flipcash.app.tokens.ui.TokenList import com.flipcash.app.tokens.ui.TokenListPresentation import com.getcode.navigation.scenes.LocalBottomSheetDismissDispatcher @@ -38,7 +39,7 @@ import kotlinx.coroutines.flow.onEach internal fun ReactionBoostCurrencySheet() { // Exit through the sheet so it animates down rather than having its scene deleted mid-frame. val dismissSheet = LocalBottomSheetDismissDispatcher.current - val viewModel = hiltViewModel() + val viewModel = hiltViewModel() val state by viewModel.stateFlow.collectAsStateWithLifecycle() Column( diff --git a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt index b20c7759ef..984f82b40d 100644 --- a/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt +++ b/apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/screens/SelectGroupCurrencySheet.kt @@ -22,6 +22,7 @@ import com.flipcash.app.tipping.internal.CreateGroupViewModel import com.flipcash.app.tipping.internal.communityCurrencies import com.flipcash.app.tipping.internal.withoutUsdf import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.flipcash.app.tokens.ui.HiltSelectTokenViewModel import com.flipcash.app.tokens.ui.TokenList import com.flipcash.app.tokens.ui.TokenListPresentation import com.flipcash.features.tipping.R @@ -48,7 +49,7 @@ internal fun SelectGroupCurrencySheet(viewModel: CreateGroupViewModel) { val dismissSheet = LocalBottomSheetDismissDispatcher.current val state by viewModel.stateFlow.collectAsStateWithLifecycle() - val tokenViewModel = hiltViewModel() + val tokenViewModel = hiltViewModel() val tokenState by tokenViewModel.stateFlow.collectAsStateWithLifecycle() val rowStyling = rememberTokenBalanceRowStyling( diff --git a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/SwapFlowScreen.kt b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/SwapFlowScreen.kt index 27122bb49b..c68a805280 100644 --- a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/SwapFlowScreen.kt +++ b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/SwapFlowScreen.kt @@ -24,6 +24,7 @@ import com.flipcash.app.core.tokens.SwapResult import com.flipcash.app.core.tokens.SwapStep import com.flipcash.app.core.tokens.TokenPurpose import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.flipcash.app.tokens.ui.HiltSelectTokenViewModel import com.flipcash.app.tokens.ui.TokenListPresentation import com.flipcash.app.tokens.ui.SwapViewModel import com.getcode.opencode.model.financial.Fiat @@ -145,7 +146,7 @@ private fun swapEntryProvider( */ @Composable private fun ConvertDestinationSelectScreen() { - val selectionViewModel = hiltViewModel() + val selectionViewModel = hiltViewModel() val viewModel = flowSharedViewModel() val flowNavigator = rememberFlowNavigator() val purpose = viewModel.stateFlow.collectAsStateWithLifecycle().value.purpose @@ -202,7 +203,7 @@ private fun ConvertDestinationSelectScreen() { */ @Composable private fun BuyFundingSelectScreen() { - val selectionViewModel = hiltViewModel() + val selectionViewModel = hiltViewModel() val viewModel = flowSharedViewModel() val flowNavigator = rememberFlowNavigator() val state = viewModel.stateFlow.collectAsStateWithLifecycle().value @@ -249,7 +250,7 @@ private fun BuyFundingSelectScreen() { @Composable private fun SwapPurchaseTokenSelectScreen(targetMint: Mint, amount: Fiat) { - val selectionViewModel = hiltViewModel() + val selectionViewModel = hiltViewModel() val viewModel = flowSharedViewModel() val flowNavigator = rememberFlowNavigator() diff --git a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenSelectScreen.kt b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenSelectScreen.kt index e6d40a21a3..ffcf401e4c 100644 --- a/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenSelectScreen.kt +++ b/apps/flipcash/features/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenSelectScreen.kt @@ -14,6 +14,7 @@ import com.flipcash.app.core.AppRoute.Transfers.Withdrawal import com.flipcash.app.core.tokens.TokenPurpose import com.flipcash.app.tokens.internal.SelectTokenScreen import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.flipcash.app.tokens.ui.HiltSelectTokenViewModel import com.flipcash.app.tokens.ui.TokenListPresentation import com.flipcash.features.tokens.R import com.getcode.navigation.core.LocalCodeNavigator @@ -33,7 +34,7 @@ fun TokenSelectScreen( loadingMint: Mint? = null, ) { val navigator = LocalCodeNavigator.current - val viewModel = hiltViewModel() + val viewModel = hiltViewModel() Column( modifier = if (presentation.wrapHeight) Modifier.fillMaxWidth() diff --git a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalFlowScreen.kt b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalFlowScreen.kt index 9cbfdf396e..e5154820c9 100644 --- a/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalFlowScreen.kt +++ b/apps/flipcash/features/withdrawal/src/main/kotlin/com/flipcash/app/withdrawal/WithdrawalFlowScreen.kt @@ -22,6 +22,7 @@ import com.flipcash.app.core.withdrawal.WithdrawalResult import com.flipcash.app.core.withdrawal.WithdrawalStep import com.flipcash.app.theme.FlipcashThemeWrapper import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.flipcash.app.tokens.ui.HiltSelectTokenViewModel import com.flipcash.app.tokens.ui.TokenList import com.flipcash.app.withdrawal.internal.screens.UsdcWithdrawalInformationScreen import com.flipcash.app.withdrawal.internal.screens.WithdrawalConfirmationScreen @@ -105,7 +106,7 @@ private fun withdrawalEntryProvider(): (NavKey) -> NavEntry = entryProvi @Composable private fun WithdrawalSelectTokenScreen() { val flowNavigator = rememberFlowNavigator() - val viewModel = hiltViewModel() + val viewModel = hiltViewModel() val state by viewModel.stateFlow.collectAsStateWithLifecycle() Column( diff --git a/apps/flipcash/shared/tokens/build.gradle.kts b/apps/flipcash/shared/tokens/build.gradle.kts index 18842e020e..75776da4b7 100644 --- a/apps/flipcash/shared/tokens/build.gradle.kts +++ b/apps/flipcash/shared/tokens/build.gradle.kts @@ -1,38 +1,74 @@ plugins { - alias(libs.plugins.flipcash.android.feature) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlin.ksp) } -android { - namespace = "${Gradle.flipcashNamespace}.shared.tokens" -} +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.shared.tokens" + // No resources of its own; SwapViewModel and TokenInfoViewModel read dependency strings through R. + androidResources { + enable = true + } + } -dependencies { - testImplementation(kotlin("test")) - testImplementation(libs.bundles.unit.testing) - testImplementation(project(":ui:resources:test-fixtures")) - testImplementation(project(":libs:currency-math:test-fixtures")) - testImplementation(libs.mockito.kotlin) + sourceSets { + commonMain.dependencies { + api(project(":apps:flipcash:shared:tokens:core")) - api(project(":apps:flipcash:shared:tokens:core")) + implementation(project(":apps:flipcash:core")) + implementation(project(":apps:flipcash:shared:persistence:sources")) + implementation(project(":services:opencode")) + implementation(project(":libs:coroutines")) + implementation(project(":libs:encryption:keys")) + implementation(project(":libs:logging")) + implementation(project(":ui:navigation")) - implementation(libs.androidx.datastore) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.androidx.datastore.preferences.core) + implementation(libs.javax.inject) + } + androidMain.dependencies { + // What flipcash.android.feature supplied before the move to KMP. + implementation(libs.bundles.hilt) + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(libs.bundles.compose) + implementation(project(":ui:core")) + implementation(project(":ui:components")) + implementation(project(":ui:resources")) + implementation(project(":ui:theme")) + implementation(project(":apps:flipcash:core-ui")) + implementation(project(":services:flipcash")) - implementation(libs.androidx.lifecycle.process) + implementation(libs.androidx.datastore) + implementation(libs.androidx.lifecycle.process) + implementation(libs.bundles.haze) - implementation(libs.bundles.haze) + implementation(project(":apps:flipcash:shared:amount-entry")) + implementation(project(":apps:flipcash:shared:funding")) + implementation(project(":apps:flipcash:shared:transaction-history")) + implementation(project(":apps:flipcash:shared:analytics")) + implementation(project(":apps:flipcash:shared:featureflags")) + implementation(project(":apps:flipcash:shared:onramp:coinbase")) + implementation(project(":apps:flipcash:shared:onramp:deeplinks")) + implementation(project(":apps:flipcash:shared:shareable")) + implementation(project(":apps:flipcash:shared:userflags")) + implementation(project(":libs:datetime")) + implementation(project(":libs:messaging")) + } + getByName("androidHostTest").dependencies { + implementation(kotlin("test")) + implementation(libs.bundles.unit.testing) + implementation(project(":ui:resources:test-fixtures")) + implementation(project(":libs:currency-math:test-fixtures")) + implementation(libs.mockito.kotlin) + implementation(project(":apps:flipcash:shared:analytics:test-fixtures")) + } + } +} - implementation(project(":apps:flipcash:shared:amount-entry")) - implementation(project(":apps:flipcash:shared:funding")) - implementation(project(":apps:flipcash:shared:transaction-history")) - implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) - implementation(project(":apps:flipcash:shared:featureflags")) - implementation(project(":apps:flipcash:shared:onramp:coinbase")) - implementation(project(":apps:flipcash:shared:onramp:deeplinks")) - implementation(project(":apps:flipcash:shared:funding")) - implementation(project(":apps:flipcash:shared:persistence:sources")) - implementation(project(":apps:flipcash:shared:shareable")) - implementation(project(":apps:flipcash:shared:userflags")) - implementation(project(":libs:datetime")) - implementation(project(":libs:messaging")) +dependencies { + add("kspAndroid", libs.hilt.android.compiler) + add("kspAndroid", libs.hilt.compiler) } diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/BalancePollerTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/BalancePollerTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/BalancePollerTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/BalancePollerTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/FeeAffordableEntryTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/FeeAffordableEntryTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/FeeAffordableEntryTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/FeeAffordableEntryTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/TokenCoordinatorMintOverloadTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/TokenCoordinatorMintOverloadTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/TokenCoordinatorMintOverloadTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/TokenCoordinatorMintOverloadTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/TokenSelectionResolverTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/TokenSelectionResolverTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/TokenSelectionResolverTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/TokenSelectionResolverTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/TokenStateRepricingTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/TokenStateRepricingTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/TokenStateRepricingTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/TokenStateRepricingTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/TransactionNotificationPromptTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/TransactionNotificationPromptTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/TransactionNotificationPromptTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/TransactionNotificationPromptTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/UsdcDepositSweepTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/UsdcDepositSweepTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/UsdcDepositSweepTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/UsdcDepositSweepTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/WalletRevealCoordinatorTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/WalletRevealCoordinatorTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/WalletRevealCoordinatorTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/WalletRevealCoordinatorTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjectionTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjectionTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjectionTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjectionTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/data/AggregationTypeTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/data/AggregationTypeTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/data/AggregationTypeTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/data/AggregationTypeTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/DollarsConversionFeeTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/DollarsConversionFeeTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/DollarsConversionFeeTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/DollarsConversionFeeTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModelTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModelTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModelTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModelTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModelStateTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModelStateTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModelStateTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModelStateTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelBalancePrefetchTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/SwapViewModelBalancePrefetchTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelBalancePrefetchTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/SwapViewModelBalancePrefetchTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelErrorTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/SwapViewModelErrorTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelErrorTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/SwapViewModelErrorTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/SwapViewModelStateTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelChartLoadTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelChartLoadTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelChartLoadTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelChartLoadTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelConvertTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelConvertTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelConvertTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelConvertTest.kt diff --git a/apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelStateTest.kt b/apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelStateTest.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/test/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelStateTest.kt rename to apps/flipcash/shared/tokens/src/androidHostTest/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModelStateTest.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/BalancePoller.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/BalancePoller.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/BalancePoller.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/BalancePoller.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/FeeAffordableEntry.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/FeeAffordableEntry.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/FeeAffordableEntry.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/FeeAffordableEntry.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/InlineCompletion.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/InlineCompletion.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/InlineCompletion.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/InlineCompletion.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenUpdater.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/TokenUpdater.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenUpdater.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/TokenUpdater.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TransactionNotificationPrompt.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/TransactionNotificationPrompt.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TransactionNotificationPrompt.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/TransactionNotificationPrompt.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/UsdcDepositSweep.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjection.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjection.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjection.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/bondingcurve/BondingCurveProjection.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/data/AggregationType.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/data/AggregationType.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/data/AggregationType.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/data/AggregationType.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/data/MarketCapPoint.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/data/MarketCapPoint.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/data/MarketCapPoint.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/data/MarketCapPoint.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/data/MarketTrend.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/data/MarketTrend.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/data/MarketTrend.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/data/MarketTrend.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/data/Period.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/data/Period.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/data/Period.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/data/Period.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/inject/PurchaseMethodModule.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/inject/PurchaseMethodModule.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/inject/PurchaseMethodModule.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/inject/PurchaseMethodModule.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/inject/TokenModule.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/inject/TokenModule.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/inject/TokenModule.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/inject/TokenModule.kt diff --git a/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/HiltSelectTokenViewModel.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/HiltSelectTokenViewModel.kt new file mode 100644 index 0000000000..741f622ea1 --- /dev/null +++ b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/HiltSelectTokenViewModel.kt @@ -0,0 +1,15 @@ +package com.flipcash.app.tokens.ui + +import com.flipcash.app.tokens.TokenCoordinator +import com.flipcash.libs.coroutines.DispatcherProvider +import com.getcode.opencode.exchange.Exchange +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +/** Hilt entry point for [SelectTokenViewModel]; obtain with `hiltViewModel()`. */ +@HiltViewModel +class HiltSelectTokenViewModel @Inject constructor( + tokenCoordinator: TokenCoordinator, + exchange: Exchange, + dispatchers: DispatcherProvider, +) : SelectTokenViewModel(tokenCoordinator, exchange, dispatchers) diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModel.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModel.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModel.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/MarketCapExplainerViewModel.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/TokenInfoViewModel.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenList.kt b/apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/TokenList.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/TokenList.kt rename to apps/flipcash/shared/tokens/src/androidMain/kotlin/com/flipcash/app/tokens/ui/TokenList.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt b/apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt similarity index 98% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt rename to apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt index 943f2c72ea..1ef303004e 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt +++ b/apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt @@ -1,18 +1,15 @@ package com.flipcash.app.tokens -import android.content.Context import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.preferences.core.PreferenceDataStoreFactory import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.datastore.preferences.preferencesDataStoreFile import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import android.os.Handler -import android.os.Looper -import androidx.lifecycle.ProcessLifecycleOwner +import com.flipcash.app.core.lifecycle.AppProcessLifecycle +import com.flipcash.app.core.storage.PreferencesFiles import com.flipcash.app.persistence.sources.TokenDataSource import com.flipcash.libs.coroutines.DispatcherProvider import com.flipcash.app.tokens.core.ReservesBalanceProvider @@ -44,7 +41,6 @@ import com.getcode.utils.TraceType import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.utils.network.retryable import com.getcode.utils.trace -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob @@ -114,7 +110,7 @@ enum class TokenSyncState { */ @Singleton class TokenCoordinator @Inject constructor( - @param:ApplicationContext private val context: Context, + private val preferencesFiles: PreferencesFiles, private val tokenController: TokenController, private val accountController: AccountController, private val networkObserver: NetworkConnectivityListener, @@ -145,7 +141,7 @@ class TokenCoordinator @Inject constructor( private val selectedToken = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }, scope = scope, - produceFile = { context.preferencesDataStoreFile("selected-token") } + produceFile = { preferencesFiles.file("selected-token") } ) private val cluster = MutableStateFlow(null) @@ -293,11 +289,9 @@ class TokenCoordinator @Inject constructor( // region Lifecycle init { - // Posted, not called directly: addObserver is main-thread-only, and this singleton is built + // Posted to the main thread on Android: addObserver is main-thread-only, and this singleton is built // off the main thread so its construction stays off the startup path. - Handler(Looper.getMainLooper()).post { - ProcessLifecycleOwner.get().lifecycle.addObserver(this) - } + AppProcessLifecycle.addObserver(this) cluster.filterNotNull() .flatMapLatest { networkObserver.state.map { it.connected } } diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenSelectionResolver.kt b/apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/TokenSelectionResolver.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenSelectionResolver.kt rename to apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/TokenSelectionResolver.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/WalletRevealCoordinator.kt b/apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/WalletRevealCoordinator.kt similarity index 100% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/WalletRevealCoordinator.kt rename to apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/WalletRevealCoordinator.kt diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt b/apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt similarity index 97% rename from apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt rename to apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt index d930f95748..40ab565b64 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt +++ b/apps/flipcash/shared/tokens/src/commonMain/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt @@ -16,7 +16,6 @@ import com.getcode.solana.keys.Mint import com.getcode.solana.keys.base58 import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.view.BaseViewModel -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -27,8 +26,11 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import javax.inject.Inject -@HiltViewModel -class SelectTokenViewModel @Inject constructor( +/** + * Shared across Android and desktop. Android obtains it through [HiltSelectTokenViewModel], since + * `@HiltViewModel` is Android-only; desktop constructs it from its Metro graph. + */ +open class SelectTokenViewModel @Inject constructor( tokenCoordinator: TokenCoordinator, exchange: Exchange, dispatchers: DispatcherProvider, From 3cbffbd57dfc6ed695d82d29ea32bc4185dba51f Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 19:10:32 -0400 Subject: [PATCH 13/17] build(balance): move features:balance to android+jvm KMP WalletViewModel becomes an open class in commonMain with an @Inject constructor; the @HiltViewModel subclass HiltWalletViewModel stays in androidMain, and the Android entry point asks Hilt for it. The wallet content and header composables move to commonMain and read their strings and drawables through CoreStrings/CoreDrawables. The view model only called ChatCoordinator.hasEverTipped(), so it now takes a one-method TipHistorySource from app:core. ChatCoordinator extends it and ChatModule binds it, so Android injects the same object and balance no longer depends on shared:chat (10k lines). ReportDrawnWhen becomes an expect seam (no-op on jvm). card-expand moves to android+jvm with everything in commonMain. --- apps/flipcash/card-expand/build.gradle.kts | 23 +++--- .../flipcash/app/cardexpand/CardExpansion.kt | 0 .../core/resources/CoreResources.android.kt | 9 +++ .../app/core/chat/TipHistorySource.kt | 14 ++++ .../app/core/resources/CoreResources.kt | 9 +++ .../app/core/resources/CoreResources.jvm.kt | 18 +++++ .../features/balance/build.gradle.kts | 80 ++++++++++++++----- .../internal/BalanceViewModelStateTest.kt | 0 .../balance/internal/BalanceViewModelTest.kt | 6 +- .../internal/WalletLoadingStateTest.kt | 0 .../internal/WalletMilestoneGatingTest.kt | 6 +- .../com/flipcash/app/balance/WalletScreen.kt | 3 +- .../balance/internal/HiltWalletViewModel.kt | 37 +++++++++ .../balance/internal/ReportDrawn.android.kt | 7 ++ .../app/balance/internal/ReportDrawn.kt | 7 ++ .../balance/internal/WalletScreenContent.kt | 30 +++---- .../app/balance/internal/WalletViewModel.kt | 14 ++-- .../internal/components/BalanceHeader.kt | 0 .../app/balance/internal/ReportDrawn.jvm.kt | 7 ++ .../flipcash/shared/chat/ChatCoordinator.kt | 5 +- .../flipcash/shared/chat/inject/ChatModule.kt | 6 ++ 21 files changed, 220 insertions(+), 61 deletions(-) rename apps/flipcash/card-expand/src/{main => commonMain}/kotlin/com/flipcash/app/cardexpand/CardExpansion.kt (100%) create mode 100644 apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/TipHistorySource.kt rename apps/flipcash/features/balance/src/{test => androidHostTest}/kotlin/com/flipcash/app/balance/internal/BalanceViewModelStateTest.kt (100%) rename apps/flipcash/features/balance/src/{test => androidHostTest}/kotlin/com/flipcash/app/balance/internal/BalanceViewModelTest.kt (96%) rename apps/flipcash/features/balance/src/{test => androidHostTest}/kotlin/com/flipcash/app/balance/internal/WalletLoadingStateTest.kt (100%) rename apps/flipcash/features/balance/src/{test => androidHostTest}/kotlin/com/flipcash/app/balance/internal/WalletMilestoneGatingTest.kt (97%) rename apps/flipcash/features/balance/src/{main => androidMain}/kotlin/com/flipcash/app/balance/WalletScreen.kt (94%) create mode 100644 apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/internal/HiltWalletViewModel.kt create mode 100644 apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.android.kt create mode 100644 apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.kt rename apps/flipcash/features/balance/src/{main => commonMain}/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt (94%) rename apps/flipcash/features/balance/src/{main => commonMain}/kotlin/com/flipcash/app/balance/internal/WalletViewModel.kt (97%) rename apps/flipcash/features/balance/src/{main => commonMain}/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt (100%) create mode 100644 apps/flipcash/features/balance/src/jvmMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.jvm.kt diff --git a/apps/flipcash/card-expand/build.gradle.kts b/apps/flipcash/card-expand/build.gradle.kts index 3def3a94a6..2eee137515 100644 --- a/apps/flipcash/card-expand/build.gradle.kts +++ b/apps/flipcash/card-expand/build.gradle.kts @@ -1,14 +1,19 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) } -android { - namespace = "${Gradle.flipcashNamespace}.cardexpand" -} - -dependencies { - implementation(libs.compose.foundation) +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.cardexpand" + } - testImplementation(kotlin("test")) - testImplementation(libs.bundles.unit.testing) + sourceSets { + commonMain.dependencies { + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.animation) + } + } } diff --git a/apps/flipcash/card-expand/src/main/kotlin/com/flipcash/app/cardexpand/CardExpansion.kt b/apps/flipcash/card-expand/src/commonMain/kotlin/com/flipcash/app/cardexpand/CardExpansion.kt similarity index 100% rename from apps/flipcash/card-expand/src/main/kotlin/com/flipcash/app/cardexpand/CardExpansion.kt rename to apps/flipcash/card-expand/src/commonMain/kotlin/com/flipcash/app/cardexpand/CardExpansion.kt diff --git a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt index a4ee6b636a..fd2dd31111 100644 --- a/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt +++ b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt @@ -14,6 +14,11 @@ actual object CoreStrings { actual val label_fromCurrencyDepreciation: Int = R.string.label_fromCurrencyDepreciation actual val title_recentActivity: Int = R.string.title_recentActivity actual val label_chatReceipt_yesterday: Int = R.string.label_chatReceipt_yesterday + actual val title_tipOnboarding: Int = R.string.title_tipOnboarding + actual val action_createYourOwnCurrency: Int = R.string.action_createYourOwnCurrency + actual val action_currencyLeaderboard: Int = R.string.action_currencyLeaderboard + actual val action_addDollars: Int = R.string.action_addDollars + actual val action_withdrawDollars: Int = R.string.action_withdrawDollars } @Suppress("PropertyName") @@ -21,4 +26,8 @@ actual object CoreDrawables { actual val ic_nav_tipcard: Int = R.drawable.ic_nav_tipcard actual val ic_checked_green: Int = R.drawable.ic_checked_green actual val ic_chevron_right: Int = R.drawable.ic_chevron_right + actual val ic_coins_add: Int = R.drawable.ic_coins_add + actual val ic_globe: Int = R.drawable.ic_globe + actual val ic_menu_deposit: Int = R.drawable.ic_menu_deposit + actual val ic_menu_withdraw: Int = R.drawable.ic_menu_withdraw } diff --git a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/TipHistorySource.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/TipHistorySource.kt new file mode 100644 index 0000000000..c6a0c8774b --- /dev/null +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/chat/TipHistorySource.kt @@ -0,0 +1,14 @@ +package com.flipcash.app.core.chat + +import kotlinx.coroutines.flow.Flow + +/** + * Whether the signed-in account has ever sent a tip, read off the local chat cache. + * + * Emits `null` until that cache is known to be complete, since an absent TIPPED message proves + * nothing before then. Split out of the chat coordinator so callers that need only this answer + * (onboarding on the wallet tab) don't depend on the whole chat module. + */ +interface TipHistorySource { + fun hasEverTipped(): Flow +} diff --git a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt index 7d67f48ab1..28e1615421 100644 --- a/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt @@ -17,6 +17,11 @@ expect object CoreStrings { val label_fromCurrencyDepreciation: Int val title_recentActivity: Int val label_chatReceipt_yesterday: Int + val title_tipOnboarding: Int + val action_createYourOwnCurrency: Int + val action_currencyLeaderboard: Int + val action_addDollars: Int + val action_withdrawDollars: Int } @Suppress("PropertyName") @@ -24,4 +29,8 @@ expect object CoreDrawables { val ic_nav_tipcard: Int val ic_checked_green: Int val ic_chevron_right: Int + val ic_coins_add: Int + val ic_globe: Int + val ic_menu_deposit: Int + val ic_menu_withdraw: Int } diff --git a/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt index 9b386346e5..92b968fcf4 100644 --- a/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt +++ b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt @@ -13,6 +13,15 @@ import com.flipcash.core.resources.title_addMoney import com.flipcash.core.resources.title_scanTipCard import com.flipcash.core.resources.title_recentActivity import com.flipcash.core.resources.label_chatReceipt_yesterday +import com.flipcash.core.resources.title_tipOnboarding +import com.flipcash.core.resources.action_createYourOwnCurrency +import com.flipcash.core.resources.action_currencyLeaderboard +import com.flipcash.core.resources.action_addDollars +import com.flipcash.core.resources.action_withdrawDollars +import com.flipcash.core.resources.ic_coins_add +import com.flipcash.core.resources.ic_globe +import com.flipcash.core.resources.ic_menu_deposit +import com.flipcash.core.resources.ic_menu_withdraw import com.getcode.util.resources.ComposeResourceIds @Suppress("PropertyName") @@ -27,6 +36,11 @@ actual object CoreStrings { actual val label_fromCurrencyDepreciation: Int = ComposeResourceIds.register(Res.string.label_fromCurrencyDepreciation) actual val title_recentActivity: Int = ComposeResourceIds.register(Res.string.title_recentActivity) actual val label_chatReceipt_yesterday: Int = ComposeResourceIds.register(Res.string.label_chatReceipt_yesterday) + actual val title_tipOnboarding: Int = ComposeResourceIds.register(Res.string.title_tipOnboarding) + actual val action_createYourOwnCurrency: Int = ComposeResourceIds.register(Res.string.action_createYourOwnCurrency) + actual val action_currencyLeaderboard: Int = ComposeResourceIds.register(Res.string.action_currencyLeaderboard) + actual val action_addDollars: Int = ComposeResourceIds.register(Res.string.action_addDollars) + actual val action_withdrawDollars: Int = ComposeResourceIds.register(Res.string.action_withdrawDollars) } @Suppress("PropertyName") @@ -34,4 +48,8 @@ actual object CoreDrawables { actual val ic_nav_tipcard: Int = ComposeResourceIds.register(Res.drawable.ic_nav_tipcard) actual val ic_checked_green: Int = ComposeResourceIds.register(Res.drawable.ic_checked_green) actual val ic_chevron_right: Int = checkNotNull(ComposeResourceIds.drawableId("ic_chevron_right")) + actual val ic_coins_add: Int = ComposeResourceIds.register(Res.drawable.ic_coins_add) + actual val ic_globe: Int = ComposeResourceIds.register(Res.drawable.ic_globe) + actual val ic_menu_deposit: Int = ComposeResourceIds.register(Res.drawable.ic_menu_deposit) + actual val ic_menu_withdraw: Int = ComposeResourceIds.register(Res.drawable.ic_menu_withdraw) } diff --git a/apps/flipcash/features/balance/build.gradle.kts b/apps/flipcash/features/balance/build.gradle.kts index 3c7e428cd6..a0f4382158 100644 --- a/apps/flipcash/features/balance/build.gradle.kts +++ b/apps/flipcash/features/balance/build.gradle.kts @@ -1,29 +1,65 @@ plugins { - alias(libs.plugins.flipcash.android.feature) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.kotlin.ksp) } -android { - namespace = "${Gradle.flipcashNamespace}.features.balance" -} - -dependencies { - testImplementation(kotlin("test")) - testImplementation(libs.bundles.unit.testing) - testImplementation(libs.bundles.compose.ui.testing) +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.features.balance" + } - implementation(libs.compose.paging) + sourceSets { + commonMain.dependencies { + implementation(project(":apps:flipcash:core")) + implementation(project(":apps:flipcash:core-ui")) + implementation(project(":apps:flipcash:card-expand")) + implementation(project(":apps:flipcash:shared:analytics")) + implementation(project(":apps:flipcash:shared:funding")) + implementation(project(":apps:flipcash:shared:tokens")) + implementation(project(":apps:flipcash:shared:transaction-history")) + implementation(project(":apps:flipcash:shared:userflags")) + implementation(project(":services:flipcash")) + implementation(project(":services:opencode")) + implementation(project(":services:opencode-compose")) + implementation(project(":libs:analytics-events")) + implementation(project(":libs:coroutines")) + implementation(project(":libs:encryption:keys")) + implementation(project(":ui:components")) + implementation(project(":ui:core")) + implementation(project(":ui:navigation")) + implementation(project(":ui:resources")) + implementation(project(":ui:theme")) - implementation(project(":apps:flipcash:shared:transaction-history")) - implementation(project(":apps:flipcash:shared:analytics")) - testImplementation(project(":apps:flipcash:shared:analytics:test-fixtures")) - implementation(project(":apps:flipcash:shared:chat")) - implementation(project(":apps:flipcash:shared:featureflags")) - implementation(project(":apps:flipcash:shared:funding")) - implementation(project(":apps:flipcash:shared:tokens")) - implementation(project(":apps:flipcash:shared:userflags")) - implementation(project(":apps:flipcash:card-expand")) - implementation(project(":libs:datetime")) - implementation(project(":libs:messaging")) - implementation(project(":libs:permissions:bindings")) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.material) + implementation(libs.jetbrains.compose.material3) + implementation(libs.androidx.paging.common) + implementation(libs.javax.inject) + } + androidMain.dependencies { + // What flipcash.android.feature supplied before the move to KMP. + implementation(libs.bundles.hilt) + implementation(project.dependencies.platform(libs.compose.bom)) + implementation(libs.bundles.compose) + implementation(libs.compose.paging) + implementation(project(":apps:flipcash:shared:featureflags")) + implementation(project(":libs:datetime")) + implementation(project(":libs:messaging")) + implementation(project(":libs:permissions:bindings")) + } + getByName("androidHostTest").dependencies { + implementation(kotlin("test")) + implementation(libs.bundles.unit.testing) + implementation(project(":apps:flipcash:shared:analytics:test-fixtures")) + implementation(project(":libs:test-utils")) + } + } +} +dependencies { + add("kspAndroid", libs.hilt.android.compiler) + add("kspAndroid", libs.hilt.compiler) } diff --git a/apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/BalanceViewModelStateTest.kt b/apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/BalanceViewModelStateTest.kt similarity index 100% rename from apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/BalanceViewModelStateTest.kt rename to apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/BalanceViewModelStateTest.kt diff --git a/apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/BalanceViewModelTest.kt b/apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/BalanceViewModelTest.kt similarity index 96% rename from apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/BalanceViewModelTest.kt rename to apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/BalanceViewModelTest.kt index a5c31598b6..b5e2ef06a9 100644 --- a/apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/BalanceViewModelTest.kt +++ b/apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/BalanceViewModelTest.kt @@ -12,7 +12,7 @@ import com.flipcash.app.funding.PurchaseMethodController import com.flipcash.app.tokens.TokenCoordinator import com.flipcash.app.tokens.WalletRevealCoordinator import com.flipcash.app.userflags.UserFlagsCoordinator -import com.flipcash.shared.chat.ChatCoordinator +import com.flipcash.app.core.chat.TipHistorySource import com.flipcash.services.internal.model.thirdparty.OnRampProvider import com.flipcash.services.user.UserManager import io.mockk.coEvery @@ -47,7 +47,7 @@ class BalanceViewModelTest { private val userManager: UserManager = mockk(relaxed = true) private val userFlags: UserFlagsCoordinator = mockk(relaxed = true) private val purchaseMethodController: PurchaseMethodController = mockk(relaxed = true) - private val chatCoordinator: ChatCoordinator = mockk(relaxed = true) + private val tipHistory: TipHistorySource = mockk(relaxed = true) private val feedCoordinator: ActivityFeedCoordinator = mockk(relaxed = true) private val tokenCoordinator: TokenCoordinator = mockk(relaxed = true) { every { hasAnyBalance } returns flowOf(false) @@ -67,7 +67,7 @@ class BalanceViewModelTest { dispatchers = dispatchers, purchaseMethodController = purchaseMethodController, analytics = analytics, - chatCoordinator = chatCoordinator, + tipHistory = tipHistory, feedCoordinator = feedCoordinator, tokenCoordinator = tokenCoordinator, walletReveal = walletReveal, diff --git a/apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/WalletLoadingStateTest.kt b/apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/WalletLoadingStateTest.kt similarity index 100% rename from apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/WalletLoadingStateTest.kt rename to apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/WalletLoadingStateTest.kt diff --git a/apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/WalletMilestoneGatingTest.kt b/apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/WalletMilestoneGatingTest.kt similarity index 97% rename from apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/WalletMilestoneGatingTest.kt rename to apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/WalletMilestoneGatingTest.kt index 384eb86e40..7818de9d95 100644 --- a/apps/flipcash/features/balance/src/test/kotlin/com/flipcash/app/balance/internal/WalletMilestoneGatingTest.kt +++ b/apps/flipcash/features/balance/src/androidHostTest/kotlin/com/flipcash/app/balance/internal/WalletMilestoneGatingTest.kt @@ -10,7 +10,7 @@ import com.flipcash.app.tokens.TokenCoordinator import com.flipcash.app.tokens.WalletRevealCoordinator import com.flipcash.app.userflags.UserFlagsCoordinator import com.flipcash.services.user.UserManager -import com.flipcash.shared.chat.ChatCoordinator +import com.flipcash.app.core.chat.TipHistorySource import com.flipcash.shared.transactionhistory.ActivityFeedCoordinator import io.mockk.every import io.mockk.mockk @@ -46,7 +46,7 @@ class WalletMilestoneGatingTest { private val hasEverTipped = MutableStateFlow(null) - private val chatCoordinator: ChatCoordinator = mockk(relaxed = true) { + private val tipHistory: TipHistorySource = mockk(relaxed = true) { every { hasEverTipped() } returns hasEverTipped } private val feedCoordinator: ActivityFeedCoordinator = mockk(relaxed = true) { @@ -68,7 +68,7 @@ class WalletMilestoneGatingTest { dispatchers = dispatchers, purchaseMethodController = mockk(relaxed = true), analytics = FlipcashAnalytics.None, - chatCoordinator = chatCoordinator, + tipHistory = tipHistory, feedCoordinator = feedCoordinator, tokenCoordinator = tokenCoordinator, walletReveal = walletReveal, diff --git a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/WalletScreen.kt b/apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/WalletScreen.kt similarity index 94% rename from apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/WalletScreen.kt rename to apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/WalletScreen.kt index 09874e18c5..87dd2b5be0 100644 --- a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/WalletScreen.kt +++ b/apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/WalletScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.flipcash.app.balance.internal.HiltWalletViewModel import com.flipcash.app.balance.internal.WalletViewModel import com.flipcash.app.balance.internal.WalletScreen import com.flipcash.app.core.AppRoute @@ -27,7 +28,7 @@ fun WalletScreen() { modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { - val viewModel = hiltViewModel() + val viewModel = hiltViewModel() val tokenViewModel = hiltViewModel() WalletScreen(viewModel, tokenViewModel) diff --git a/apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/internal/HiltWalletViewModel.kt b/apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/internal/HiltWalletViewModel.kt new file mode 100644 index 0000000000..eed4d2439b --- /dev/null +++ b/apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/internal/HiltWalletViewModel.kt @@ -0,0 +1,37 @@ +package com.flipcash.app.balance.internal + +import com.flipcash.app.analytics.FlipcashAnalytics +import com.flipcash.app.core.chat.TipHistorySource +import com.flipcash.app.funding.PurchaseMethodController +import com.flipcash.app.tokens.TokenCoordinator +import com.flipcash.app.tokens.WalletRevealCoordinator +import com.flipcash.app.userflags.UserFlagsCoordinator +import com.flipcash.libs.coroutines.DispatcherProvider +import com.flipcash.services.user.UserManager +import com.flipcash.shared.transactionhistory.ActivityFeedCoordinator +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +/** Hilt entry point for the shared [WalletViewModel]. */ +@HiltViewModel +internal class HiltWalletViewModel @Inject constructor( + userManager: UserManager, + userFlags: UserFlagsCoordinator, + dispatchers: DispatcherProvider, + purchaseMethodController: PurchaseMethodController, + analytics: FlipcashAnalytics, + tipHistory: TipHistorySource, + feedCoordinator: ActivityFeedCoordinator, + tokenCoordinator: TokenCoordinator, + walletReveal: WalletRevealCoordinator, +) : WalletViewModel( + userManager = userManager, + userFlags = userFlags, + dispatchers = dispatchers, + purchaseMethodController = purchaseMethodController, + analytics = analytics, + tipHistory = tipHistory, + feedCoordinator = feedCoordinator, + tokenCoordinator = tokenCoordinator, + walletReveal = walletReveal, +) diff --git a/apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.android.kt b/apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.android.kt new file mode 100644 index 0000000000..bb7a0171f8 --- /dev/null +++ b/apps/flipcash/features/balance/src/androidMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.android.kt @@ -0,0 +1,7 @@ +package com.flipcash.app.balance.internal + +import androidx.activity.compose.ReportDrawnWhen +import androidx.compose.runtime.Composable + +@Composable +internal actual fun ReportDrawnWhenSeam(predicate: () -> Boolean) = ReportDrawnWhen(predicate) diff --git a/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.kt b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.kt new file mode 100644 index 0000000000..a166ae2457 --- /dev/null +++ b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.kt @@ -0,0 +1,7 @@ +package com.flipcash.app.balance.internal + +import androidx.compose.runtime.Composable + +/** Reports the screen as fully drawn once [predicate] is true. Android forwards to `ReportDrawnWhen`. */ +@Composable +internal expect fun ReportDrawnWhenSeam(predicate: () -> Boolean) diff --git a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt similarity index 94% rename from apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt rename to apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt index 27235aaa38..5e5335bfed 100644 --- a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt +++ b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt @@ -1,6 +1,5 @@ package com.flipcash.app.balance.internal -import androidx.activity.compose.ReportDrawnWhen import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -32,8 +31,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.flipcash.app.cardexpand.CardExpansionController @@ -55,7 +52,10 @@ import com.flipcash.app.core.navigation.LocalTabBarPadding import com.flipcash.app.core.ui.TileButton import com.flipcash.app.core.ui.TileButtonStyle import com.flipcash.app.tokens.ui.SelectTokenViewModel -import com.flipcash.features.balance.R +import com.flipcash.app.core.resources.CoreDrawables +import com.flipcash.app.core.resources.CoreStrings +import com.getcode.util.resources.painterForResId +import com.getcode.util.resources.stringRes import com.flipcash.shared.transactionhistory.recentActivitySection import com.getcode.theme.CodeTheme import com.getcode.ui.theme.CodeCircularProgressIndicator @@ -86,7 +86,7 @@ internal fun WalletScreenContent( // below means "landed" is not the same frame as "composed" -- registering a reporter here holds // the activity's report open until the content below the gate has actually been drawn. Keyed to // the same condition as that gate: what the launch is finished for is the balance and the deck. - ReportDrawnWhen { !tokenState.isAwaitingTokens } + ReportDrawnWhenSeam { !tokenState.isAwaitingTokens } // The tab waits on the token set and nothing else. Tokens are what the balance and the card deck // are made of, so there is no drawing the tab without them; the activity preview and the tutorial @@ -164,7 +164,7 @@ internal fun WalletScreenContent( state = listState, // Only ever in the tree past the loading gate above, so it is the anchor for "the wallet // drew its content" -- what the startup benchmark waits on before it stops the trace. Same - // condition as ReportDrawnWhen above, so the trace stays open exactly as long as the report. + // condition as ReportDrawnWhenSeam above, so the trace stays open exactly as long as the report. modifier = Modifier .fillMaxSize() .testTag("wallet_content"), @@ -213,7 +213,7 @@ internal fun WalletScreenContent( NewUserTutorial( modifier = Modifier.fillMaxWidth() .padding(bottom = CodeTheme.dimens.grid.x5), - title = stringResource(R.string.title_tipOnboarding), + title = stringRes(CoreStrings.title_tipOnboarding), items = items, ) { item -> when (item) { @@ -322,8 +322,8 @@ internal fun WalletScreenContent( .weight(1f) .aspectRatio(1.45f), style = TileButtonStyle.Spread, - text = stringResource(R.string.action_createYourOwnCurrency), - icon = painterResource(R.drawable.ic_coins_add), + text = stringRes(CoreStrings.action_createYourOwnCurrency), + icon = painterForResId(CoreDrawables.ic_coins_add), ) { dispatchEvent(WalletViewModel.Event.OpenScreen(AppRoute.Token.CurrencyCreator)) } @@ -333,8 +333,8 @@ internal fun WalletScreenContent( .weight(1f) .aspectRatio(1.45f), style = TileButtonStyle.Spread, - text = stringResource(R.string.action_currencyLeaderboard), - icon = painterResource(R.drawable.ic_globe), + text = stringRes(CoreStrings.action_currencyLeaderboard), + icon = painterForResId(CoreDrawables.ic_globe), ) { dispatchEvent(WalletViewModel.Event.OpenScreen(AppRoute.Token.Discovery)) } @@ -351,8 +351,8 @@ internal fun WalletScreenContent( .weight(1f) .aspectRatio(1.45f), style = TileButtonStyle.Spread, - text = stringResource(R.string.action_addDollars), - icon = painterResource(R.drawable.ic_menu_deposit), + text = stringRes(CoreStrings.action_addDollars), + icon = painterForResId(CoreDrawables.ic_menu_deposit), ) { dispatchEvent(WalletViewModel.Event.PresentDepositOptions) } @@ -362,8 +362,8 @@ internal fun WalletScreenContent( .weight(1f) .aspectRatio(1.45f), style = TileButtonStyle.Spread, - text = stringResource(R.string.action_withdrawDollars), - icon = painterResource(R.drawable.ic_menu_withdraw), + text = stringRes(CoreStrings.action_withdrawDollars), + icon = painterForResId(CoreDrawables.ic_menu_withdraw), ) { // No preselected mint: the flow opens on the currency picker, which lists // every balance (Dollars included). diff --git a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletViewModel.kt b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletViewModel.kt similarity index 97% rename from apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletViewModel.kt rename to apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletViewModel.kt index b4c2826d1a..7a186f9ca0 100644 --- a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/WalletViewModel.kt +++ b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletViewModel.kt @@ -6,6 +6,7 @@ import com.flipcash.analytics.events.AddMoneyEvents import com.flipcash.app.analytics.FlipcashAnalytics import com.flipcash.app.core.ui.onboarding.TutorialItem import com.flipcash.app.core.AppRoute +import com.flipcash.app.core.chat.TipHistorySource import com.flipcash.shared.transactionhistory.ActivityFeedCoordinator import com.flipcash.shared.transactionhistory.FeedSyncState import com.flipcash.shared.transactionhistory.TransactionListItem @@ -14,13 +15,11 @@ import com.flipcash.app.tokens.TokenCoordinator import com.flipcash.app.tokens.WalletReveal import com.flipcash.app.tokens.WalletRevealCoordinator import com.flipcash.app.userflags.UserFlagsCoordinator -import com.flipcash.shared.chat.ChatCoordinator import com.flipcash.services.internal.model.thirdparty.OnRampProvider import com.flipcash.services.user.AuthState import com.flipcash.services.user.UserManager import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.view.BaseViewModel -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance @@ -30,14 +29,17 @@ import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import javax.inject.Inject -@HiltViewModel -internal class WalletViewModel @Inject constructor( +/** + * Shared wallet-tab view model. Android injects it through [HiltWalletViewModel]; desktop builds it + * from its own graph. + */ +open class WalletViewModel @Inject constructor( userManager: UserManager, userFlags: UserFlagsCoordinator, dispatchers: DispatcherProvider, purchaseMethodController: PurchaseMethodController, analytics: FlipcashAnalytics, - chatCoordinator: ChatCoordinator, + tipHistory: TipHistorySource, feedCoordinator: ActivityFeedCoordinator, tokenCoordinator: TokenCoordinator, walletReveal: WalletRevealCoordinator, @@ -191,7 +193,7 @@ internal class WalletViewModel @Inject constructor( // row to prove it, and would otherwise be told to add money it already has. combine( feedCoordinator.hasEverReceivedMoney(), - chatCoordinator.hasEverTipped(), + tipHistory.hasEverTipped(), tokenCoordinator.hasAnyBalance, ) { hasReceivedMoney, hasTipped, holdsBalance -> // A null tip milestone means the chat cache has not been reconciled yet, and an diff --git a/apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt similarity index 100% rename from apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt rename to apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/components/BalanceHeader.kt diff --git a/apps/flipcash/features/balance/src/jvmMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.jvm.kt b/apps/flipcash/features/balance/src/jvmMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.jvm.kt new file mode 100644 index 0000000000..d9254091b8 --- /dev/null +++ b/apps/flipcash/features/balance/src/jvmMain/kotlin/com/flipcash/app/balance/internal/ReportDrawn.jvm.kt @@ -0,0 +1,7 @@ +package com.flipcash.app.balance.internal + +import androidx.compose.runtime.Composable + +// Desktop has no fully-drawn reporter. +@Composable +internal actual fun ReportDrawnWhenSeam(predicate: () -> Boolean) = Unit diff --git a/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/ChatCoordinator.kt b/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/ChatCoordinator.kt index da8b75f88c..82b9b12bfe 100644 --- a/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/ChatCoordinator.kt +++ b/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/ChatCoordinator.kt @@ -4,6 +4,7 @@ package com.flipcash.shared.chat import androidx.paging.ExperimentalPagingApi import androidx.paging.PagingData +import com.flipcash.app.core.chat.TipHistorySource import com.flipcash.app.core.contacts.DeviceContact import com.flipcash.services.models.UserProfile import com.flipcash.services.models.chat.ChatId @@ -262,7 +263,7 @@ interface DmChatResolver { * * Implemented by [com.flipcash.shared.chat.internal.delegates.MessagingDelegate]. */ -interface MessagingOperations { +interface MessagingOperations : TipHistorySource { /** * Returns the other member of a DM (fetching from the server and persisting * if not cached locally), or `null` if it can't be resolved. Chat-type @@ -311,7 +312,7 @@ interface MessagingOperations { * [ChatHydrationState.Unavailable], so an unreachable server ends the wait instead of extending * it forever. */ - fun hasEverTipped(): Flow + override fun hasEverTipped(): Flow /** Observes messages in [chatId] via Paging 3, with remote-mediated page loads. */ fun observeMessagesPaged(chatId: ChatId): Flow> diff --git a/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/inject/ChatModule.kt b/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/inject/ChatModule.kt index fc277f5f8a..a5a53c5104 100644 --- a/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/inject/ChatModule.kt +++ b/apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/inject/ChatModule.kt @@ -3,6 +3,7 @@ package com.flipcash.shared.chat.inject import com.flipcash.shared.chat.link.LinkPreviewImages import com.flipcash.shared.chat.link.UploadedLinkPreviewImages import com.flipcash.shared.chat.ChatArchiveStore +import com.flipcash.app.core.chat.TipHistorySource import com.flipcash.shared.chat.ChatCoordinator import com.flipcash.shared.chat.ChatDraftStore import com.flipcash.shared.chat.HostLobbies @@ -40,6 +41,11 @@ abstract class ChatModule { impl: RealChatCoordinator ): ChatCoordinator + @Binds + abstract fun bindTipHistorySource( + impl: ChatCoordinator + ): TipHistorySource + @Binds @Singleton abstract fun bindChatDraftStore( From 63a2175113ffe2237d8cc620f90cf781c8f96135 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 19:38:20 -0400 Subject: [PATCH 14/17] feat(desktop): Metro graph and wallet window Desktop builds WalletViewModel from a Metro graph that reuses the Dagger modules Android's Hilt graph installs. OpenCodeModule and FlipcashModule split into shared providers (commonMain), the Android channel/protocol config (androidMain) and an okhttp channel module in apps/desktop. Internal @Provides functions carry @JvmName so Dagger's factory names stay unmangled for Metro's runtime interop. DesktopModule binds the ten seams Android provides from Hilt modules; funding, analytics and connectivity are stubs. shared:theme moves to common so the window paints the Flipcash colour scheme. --- apps/desktop/build.gradle.kts | 39 ++++ .../com/flipcash/desktop/DesktopGraph.kt | 39 +++- .../com/flipcash/desktop/DesktopModule.kt | 111 +++++++++++ .../flipcash/desktop/DesktopWalletContent.kt | 45 +++++ .../main/kotlin/com/flipcash/desktop/Main.kt | 14 +- .../com/flipcash/desktop/SnapshotMain.kt | 32 ++++ .../inject/FlipcashDesktopChannelModule.kt | 66 +++++++ .../inject/OpenCodeDesktopChannelModule.kt | 55 ++++++ apps/flipcash/core-ui/build.gradle.kts | 2 + apps/flipcash/core/build.gradle.kts | 2 + .../features/balance/build.gradle.kts | 2 + .../balance/internal/WalletScreenContent.kt | 2 +- .../shared/analytics/build.gradle.kts | 2 + apps/flipcash/shared/funding/build.gradle.kts | 2 + .../persistence/sources/build.gradle.kts | 2 + apps/flipcash/shared/theme/build.gradle.kts | 26 ++- .../com/flipcash/app/theme/FlipcashPreview.kt | 0 .../com/flipcash/app/theme/FlipcashTheme.kt | 0 .../theme/internal/FlipcashDesignSystem.kt | 0 apps/flipcash/shared/tokens/build.gradle.kts | 2 + .../transaction-history/build.gradle.kts | 2 + .../shared/userflags/build.gradle.kts | 2 + gradle/libs.versions.toml | 2 + services/flipcash/build.gradle.kts | 2 + .../services/inject/FlipcashChannelModule.kt | 178 ++++++++++++++++++ .../services/inject/ChatCryptoModule.kt | 6 +- .../services/inject/FlipcashModule.kt | 112 +++-------- services/opencode/build.gradle.kts | 2 + .../com/getcode/opencode/RepositoryFactory.kt | 15 +- .../opencode/inject/OpenCodeChannelModule.kt | 93 +++++++++ .../getcode/opencode/inject/OpenCodeModule.kt | 91 +-------- .../opencode/inject/SessionListenersModule.kt | 0 .../opencode/inject/TransactorModule.kt | 0 .../annotations/OpenCodeManagedChannel.kt | 4 +- .../opencode/internal/annotations/Protocol.kt | 2 +- ui/navigation/build.gradle.kts | 2 + 36 files changed, 751 insertions(+), 205 deletions(-) create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopWalletContent.kt create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/inject/FlipcashDesktopChannelModule.kt create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/inject/OpenCodeDesktopChannelModule.kt rename apps/flipcash/shared/theme/src/{main => androidMain}/kotlin/com/flipcash/app/theme/FlipcashPreview.kt (100%) rename apps/flipcash/shared/theme/src/{main => commonMain}/kotlin/com/flipcash/app/theme/FlipcashTheme.kt (100%) rename apps/flipcash/shared/theme/src/{main => commonMain}/kotlin/com/flipcash/app/theme/internal/FlipcashDesignSystem.kt (100%) create mode 100644 services/flipcash/src/androidMain/kotlin/com/flipcash/services/inject/FlipcashChannelModule.kt rename services/flipcash/src/{androidMain => commonMain}/kotlin/com/flipcash/services/inject/FlipcashModule.kt (75%) create mode 100644 services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/OpenCodeChannelModule.kt rename services/opencode/src/{androidMain => commonMain}/kotlin/com/getcode/opencode/inject/OpenCodeModule.kt (62%) rename services/opencode/src/{androidMain => commonMain}/kotlin/com/getcode/opencode/inject/SessionListenersModule.kt (100%) rename services/opencode/src/{androidMain => commonMain}/kotlin/com/getcode/opencode/inject/TransactorModule.kt (100%) diff --git a/apps/desktop/build.gradle.kts b/apps/desktop/build.gradle.kts index 2fc3dfc4c3..f0dd564dc9 100644 --- a/apps/desktop/build.gradle.kts +++ b/apps/desktop/build.gradle.kts @@ -11,9 +11,40 @@ kotlin { jvmToolchain(21) } +metro { + interop { + includeJavax() + includeDagger() + enableDaggerRuntimeInterop.set(true) + } +} + dependencies { implementation(compose.desktop.currentOs) implementation(libs.jetbrains.compose.material3) + implementation(project(":services:opencode")) + implementation(project(":apps:flipcash:features:balance")) + implementation(project(":apps:flipcash:core")) + implementation(project(":apps:flipcash:shared:transaction-history")) + implementation(project(":apps:flipcash:shared:tokens")) + implementation(project(":apps:flipcash:shared:funding")) + implementation(project(":apps:flipcash:shared:userflags")) + implementation(project(":apps:flipcash:shared:analytics")) + implementation(project(":apps:flipcash:shared:persistence:sources")) + implementation(project(":apps:flipcash:shared:persistence:db")) + implementation(project(":services:flipcash")) + implementation(project(":libs:coroutines")) + implementation(project(":libs:logging")) + implementation(project(":ui:resources")) + implementation(project(":ui:theme")) + implementation(project(":apps:flipcash:shared:theme")) + implementation(project(":services:opencode-compose")) + implementation(project(":libs:locale:public")) + implementation(project(":libs:network:connectivity:public")) + implementation(project(":libs:encryption:chat-cipher")) + implementation(libs.kotlinx.coroutines.swing) + implementation(libs.dagger) + implementation(libs.grpc.okhttp) } compose.desktop { @@ -26,3 +57,11 @@ compose.desktop { } } } + +tasks.register("snapshot") { + group = "application" + description = "Renders the wallet screen off-screen to a PNG (-Pout=)." + classpath = sourceSets["main"].runtimeClasspath + mainClass.set("com.flipcash.desktop.SnapshotMainKt") + args(providers.gradleProperty("out").getOrElse(layout.buildDirectory.file("wallet.png").get().asFile.path)) +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt index 78b62aade5..91c09bb3ba 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt @@ -1,13 +1,40 @@ package com.flipcash.desktop +import com.flipcash.app.analytics.FlipcashAnalytics +import com.flipcash.app.balance.internal.WalletViewModel +import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.getcode.opencode.exchange.Exchange +import com.getcode.util.resources.ResourceHelper +import com.getcode.utils.network.NetworkConnectivityListener +import com.flipcash.desktop.inject.FlipcashDesktopChannelModule +import com.flipcash.services.inject.FlipcashModule +import com.flipcash.desktop.inject.OpenCodeDesktopChannelModule +import com.getcode.opencode.inject.OpenCodeModule +import com.getcode.opencode.inject.SessionListenerModule +import com.getcode.opencode.inject.TransactorModule import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.DependencyGraph -import dev.zacsweers.metro.Provides +import javax.inject.Singleton -@DependencyGraph(AppScope::class) +@Singleton +@DependencyGraph( + scope = AppScope::class, + bindingContainers = [ + OpenCodeModule::class, + OpenCodeDesktopChannelModule::class, + SessionListenerModule::class, + TransactorModule::class, + FlipcashModule::class, + com.flipcash.services.inject.ChatCryptoModule::class, + FlipcashDesktopChannelModule::class, + DesktopModule::class, + ], +) interface DesktopGraph { - val appName: String - - @Provides - fun provideAppName(): String = "Flipcash" + val walletViewModel: WalletViewModel + val selectTokenViewModel: SelectTokenViewModel + val exchange: Exchange + val resources: ResourceHelper + val networkObserver: NetworkConnectivityListener + val analytics: FlipcashAnalytics } diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt new file mode 100644 index 0000000000..ee09e944c0 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt @@ -0,0 +1,111 @@ +package com.flipcash.desktop + +import com.flipcash.app.analytics.FlipcashAnalytics +import com.flipcash.app.core.AppRoute +import com.flipcash.app.core.chat.TipHistorySource +import com.flipcash.app.core.storage.DirectoryPreferencesFiles +import com.flipcash.app.core.storage.PreferencesFiles +import com.flipcash.app.funding.PurchaseMethod +import com.flipcash.app.funding.PurchaseMethodController +import com.flipcash.app.funding.PurchaseMethodMetadata +import com.flipcash.app.funding.PurchaseMethodSelection +import com.flipcash.app.funding.PurchaseMethodState +import com.flipcash.app.persistence.sources.ChatMessageDataSource +import com.flipcash.app.tokens.TokenCoordinator +import com.flipcash.app.userflags.UserFlagsFiles +import com.flipcash.libs.coroutines.DispatcherProvider +import com.getcode.chatcipher.ChatCipher +import com.getcode.chatcipher.DefaultChatCipher +import com.getcode.opencode.providers.TokenMetadataProvider +import com.getcode.util.locale.LocaleHelper +import com.getcode.util.resources.ComposeResourceHelper +import com.getcode.util.resources.ResourceHelper +import com.getcode.utils.network.NetworkConnectivityListener +import com.getcode.utils.network.NetworkObserverStub +import dagger.Module +import dagger.Provides +import java.io.File +import java.util.Currency +import java.util.Locale +import javax.inject.Singleton +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map + +/** + * Desktop implementations of the seams Android binds in its Hilt modules. Funding, analytics and + * connectivity are stubs for this slice; the rest are real. + */ +@Module +object DesktopModule { + private val appDir: File by lazy { + File(System.getProperty("user.home"), ".flipcash").apply { mkdirs() } + } + + @Provides + @Singleton + fun dispatchers(): DispatcherProvider = object : DispatcherProvider { + override val Default: CoroutineDispatcher = Dispatchers.Default + override val Main: CoroutineDispatcher = Dispatchers.Main + override val IO: CoroutineDispatcher = Dispatchers.IO + } + + // ChatCryptoModule provides this from its companion object, which Metro's Dagger interop + // does not read from a precompiled module. + @Provides + fun chatCipher(): ChatCipher = DefaultChatCipher + + @Provides + fun analytics(): FlipcashAnalytics = FlipcashAnalytics.None + + @Provides + @Singleton + fun locale(): LocaleHelper = object : LocaleHelper { + override fun getLanguageTag(): String = Locale.getDefault().toLanguageTag() + override fun getDefaultCurrencyName(): String = + runCatching { Currency.getInstance(Locale.getDefault()).currencyCode }.getOrDefault("USD") + override suspend fun getDefaultCountry(): String? = + Locale.getDefault().country.takeIf { it.length == 2 } + } + + @Provides + @Singleton + fun network(): NetworkConnectivityListener = NetworkObserverStub() + + @Provides + @Singleton + fun preferencesFiles(): PreferencesFiles = DirectoryPreferencesFiles(appDir) + + @Provides + @Singleton + fun userFlagsFiles(): UserFlagsFiles = object : UserFlagsFiles { + override val noBackupDir: File = File(appDir, "no_backup").apply { mkdirs() } + override fun dataStoreFile(name: String): File = File(appDir, "datastore/$name.preferences_pb") + } + + @Provides + @Singleton + fun resources(): ResourceHelper = ComposeResourceHelper() + + @Provides + fun tipHistory(messages: ChatMessageDataSource): TipHistorySource = object : TipHistorySource { + override fun hasEverTipped(): Flow = messages.hasEverTipped().map { it } + } + + @Provides + fun tokenMetadata(coordinator: TokenCoordinator): TokenMetadataProvider = coordinator + + @Provides + @Singleton + fun purchaseMethods(): PurchaseMethodController = object : PurchaseMethodController { + override val state: StateFlow = MutableStateFlow(PurchaseMethodState()) + override val selections: Flow = emptyFlow() + override fun present(metadata: PurchaseMethodMetadata) = Unit + override fun select(method: PurchaseMethod, metadata: PurchaseMethodMetadata) = Unit + override suspend fun presentDepositOptions(popToRoot: Boolean): AppRoute? = null + } +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopWalletContent.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopWalletContent.kt new file mode 100644 index 0000000000..b344cab896 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopWalletContent.kt @@ -0,0 +1,45 @@ +package com.flipcash.desktop + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.flipcash.app.analytics.LocalAnalytics +import com.flipcash.app.balance.internal.WalletScreen +import com.flipcash.app.core.tokens.TokenPurpose +import com.flipcash.app.theme.FlipcashTheme +import com.flipcash.app.tokens.ui.SelectTokenViewModel +import com.getcode.opencode.compose.LocalExchange +import com.getcode.theme.CodeTheme +import com.getcode.util.resources.LocalResources +import com.getcode.utils.network.LocalNetworkObserver + +/** + * The wallet screen with the locals and window background that Android's app root provides + * (`App.kt` paints [CodeTheme.colors.background] behind every screen). The graph doesn't scope view + * models, so each read of [DesktopGraph.walletViewModel] builds a new one; they're held here. + */ +@Composable +fun DesktopWalletContent(graph: DesktopGraph) { + val walletViewModel = remember { graph.walletViewModel } + val tokenViewModel = remember { graph.selectTokenViewModel } + LaunchedEffect(tokenViewModel) { + tokenViewModel.dispatchEvent(SelectTokenViewModel.Event.OnPurposeChanged(TokenPurpose.Balance)) + } + FlipcashTheme { + CompositionLocalProvider( + LocalExchange provides graph.exchange, + LocalResources provides graph.resources, + LocalNetworkObserver provides graph.networkObserver, + LocalAnalytics provides graph.analytics, + ) { + Box(Modifier.fillMaxSize().background(CodeTheme.colors.background)) { + WalletScreen(walletViewModel, tokenViewModel) + } + } + } +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt index 8f9fbed3a2..a56821ba1e 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt @@ -1,20 +1,22 @@ package com.flipcash.desktop -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState import dev.zacsweers.metro.createGraph import java.lang.management.ManagementFactory fun main() { val graph = createGraph() application { - Window(onCloseRequest = ::exitApplication, title = "Flipcash") { - MaterialTheme { - Text(graph.appName) - } + Window( + onCloseRequest = ::exitApplication, + title = "Flipcash", + state = rememberWindowState(width = 420.dp, height = 860.dp), + ) { + DesktopWalletContent(graph) LaunchedEffect(Unit) { val sinceJvmStart = System.currentTimeMillis() - ManagementFactory.getRuntimeMXBean().startTime diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt new file mode 100644 index 0000000000..c3fd176163 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt @@ -0,0 +1,32 @@ +package com.flipcash.desktop + +import androidx.compose.ui.ImageComposeScene +import androidx.compose.ui.unit.Density +import androidx.compose.ui.use +import dev.zacsweers.metro.createGraph +import java.io.File +import kotlin.system.exitProcess +import org.jetbrains.skia.EncodedImageFormat + +/** + * Renders the wallet screen off-screen through the same graph as [main] and writes a PNG. + * Usage: `./gradlew :apps:desktop:snapshot -Pout=`. + */ +fun main(args: Array) { + val out = File(args.firstOrNull() ?: "wallet.png") + val graph = createGraph() + ImageComposeScene(width = 840, height = 1720, density = Density(2f)) { + DesktopWalletContent(graph) + }.use { scene -> + var nanos = 0L + repeat(80) { + scene.render(nanos) + Thread.sleep(100) + nanos += 100_000_000L + } + val png = scene.render(nanos).encodeToData(EncodedImageFormat.PNG)!!.bytes + out.writeBytes(png) + } + println("snapshot written to ${out.absolutePath}") + exitProcess(0) +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/inject/FlipcashDesktopChannelModule.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/inject/FlipcashDesktopChannelModule.kt new file mode 100644 index 0000000000..c9617ee0ad --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/inject/FlipcashDesktopChannelModule.kt @@ -0,0 +1,66 @@ +package com.flipcash.desktop.inject + +import com.flipcash.services.ForegroundGate +import com.flipcash.services.internal.annotations.FlipcashManagedChannel +import com.flipcash.services.internal.annotations.FlipcashManagedStreamingChannel +import com.flipcash.services.internal.annotations.FlipcashProtocol +import com.flipcash.services.user.AnalyticsIdentifier +import com.getcode.opencode.ProtocolConfig +import com.getcode.opencode.utils.logging.LoggingClientInterceptor +import dagger.Module +import dagger.Provides +import io.grpc.ManagedChannel +import io.grpc.okhttp.OkHttpChannelBuilder +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +/** + * Desktop counterpart of the Android `FlipcashChannelModule`: plain okhttp channels with the + * Android settings, no analytics identity (desktop has no Mixpanel), and a gate that is always open + * because a desktop process has no background state. + */ +@Module +object FlipcashDesktopChannelModule { + @Provides + fun provideAnalyticsIdentifier(): AnalyticsIdentifier = AnalyticsIdentifier { } + + @Provides + fun providesForegroundGate(): ForegroundGate = ForegroundGate { } + + @Singleton + @FlipcashProtocol + @Provides + fun providesFlipcashProtocolConfig(): ProtocolConfig = object : ProtocolConfig { + override val baseUrl: String + get() = "fc-v2.api.flipcash-infra.net" + override val userAgent: String + get() = "Flipcash/Desktop/${System.getProperty("os.name")}" + } + + @Singleton + @Provides + @FlipcashManagedChannel + fun provideManagedChannel( + @FlipcashProtocol + config: ProtocolConfig, + ): ManagedChannel = OkHttpChannelBuilder.forAddress(config.baseUrl, config.port) + .userAgent(config.userAgent) + .keepAliveWithoutCalls(false) + .idleTimeout(5, TimeUnit.MINUTES) + .intercept(LoggingClientInterceptor()) + .build() + + @Singleton + @Provides + @FlipcashManagedStreamingChannel + fun provideManagedStreamingChannel( + @FlipcashProtocol + config: ProtocolConfig, + ): ManagedChannel = OkHttpChannelBuilder.forAddress(config.baseUrl, config.port) + .userAgent(config.userAgent) + .keepAliveTime(config.keepAlive.inWholeMilliseconds, TimeUnit.MILLISECONDS) + .keepAliveTimeout(config.keepAliveTimeout.inWholeMilliseconds, TimeUnit.MILLISECONDS) + .keepAliveWithoutCalls(true) + .intercept(LoggingClientInterceptor()) + .build() +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/inject/OpenCodeDesktopChannelModule.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/inject/OpenCodeDesktopChannelModule.kt new file mode 100644 index 0000000000..caaae7a636 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/inject/OpenCodeDesktopChannelModule.kt @@ -0,0 +1,55 @@ +package com.flipcash.desktop.inject + +import com.getcode.opencode.ProtocolConfig +import com.getcode.opencode.internal.annotations.OpenCodeManagedChannel +import com.getcode.opencode.internal.annotations.OpenCodeManagedStreamingChannel +import com.getcode.opencode.internal.annotations.OpenCodeProtocol +import com.getcode.opencode.utils.logging.LoggingClientInterceptor +import dagger.Module +import dagger.Provides +import io.grpc.ManagedChannel +import io.grpc.okhttp.OkHttpChannelBuilder +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +/** + * Desktop counterpart of the Android `OpenCodeChannelModule`: the same channel settings, built on + * plain okhttp instead of `AndroidChannelBuilder` (which only adds Android network-change hooks). + */ +@Module +object OpenCodeDesktopChannelModule { + @Singleton + @OpenCodeProtocol + @Provides + fun providesOpenCodeProtocolConfig(): ProtocolConfig = object : ProtocolConfig { + override val baseUrl: String + get() = "ocp-v2.api.flipcash-infra.net" + override val userAgent: String + get() = "OpenCodeProtocol/Desktop/${System.getProperty("os.name")}" + } + + @Singleton + @Provides + @OpenCodeManagedChannel + fun provideManagedChannel( + @OpenCodeProtocol + config: ProtocolConfig, + ): ManagedChannel = OkHttpChannelBuilder.forAddress(config.baseUrl, config.port) + .userAgent(config.userAgent) + .keepAliveWithoutCalls(false) + .idleTimeout(5, TimeUnit.MINUTES) + .intercept(LoggingClientInterceptor()) + .build() + + @Singleton + @Provides + @OpenCodeManagedStreamingChannel + fun provideManagedStreamingChannel( + @OpenCodeProtocol + config: ProtocolConfig, + ): ManagedChannel = OkHttpChannelBuilder.forAddress(config.baseUrl, config.port) + .userAgent(config.userAgent) + .keepAliveWithoutCalls(true) + .intercept(LoggingClientInterceptor()) + .build() +} diff --git a/apps/flipcash/core-ui/build.gradle.kts b/apps/flipcash/core-ui/build.gradle.kts index f457abba5f..5c27c4add1 100644 --- a/apps/flipcash/core-ui/build.gradle.kts +++ b/apps/flipcash/core-ui/build.gradle.kts @@ -55,4 +55,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/apps/flipcash/core/build.gradle.kts b/apps/flipcash/core/build.gradle.kts index d7da03a813..2fab73b963 100644 --- a/apps/flipcash/core/build.gradle.kts +++ b/apps/flipcash/core/build.gradle.kts @@ -92,4 +92,6 @@ compose.resources { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/apps/flipcash/features/balance/build.gradle.kts b/apps/flipcash/features/balance/build.gradle.kts index a0f4382158..5ee4523ab2 100644 --- a/apps/flipcash/features/balance/build.gradle.kts +++ b/apps/flipcash/features/balance/build.gradle.kts @@ -62,4 +62,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt index 5e5335bfed..0980c6a663 100644 --- a/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt +++ b/apps/flipcash/features/balance/src/commonMain/kotlin/com/flipcash/app/balance/internal/WalletScreenContent.kt @@ -63,7 +63,7 @@ import com.getcode.ui.theme.CodeCircularProgressIndicator private const val TokenStackKey = "tokenStack" @Composable -internal fun WalletScreen( +fun WalletScreen( viewModel: WalletViewModel, tokenViewModel: SelectTokenViewModel, ) { diff --git a/apps/flipcash/shared/analytics/build.gradle.kts b/apps/flipcash/shared/analytics/build.gradle.kts index af3b9fdf86..a93d95a77b 100644 --- a/apps/flipcash/shared/analytics/build.gradle.kts +++ b/apps/flipcash/shared/analytics/build.gradle.kts @@ -37,4 +37,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/apps/flipcash/shared/funding/build.gradle.kts b/apps/flipcash/shared/funding/build.gradle.kts index 871af57bf3..23ec4527ef 100644 --- a/apps/flipcash/shared/funding/build.gradle.kts +++ b/apps/flipcash/shared/funding/build.gradle.kts @@ -46,4 +46,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/apps/flipcash/shared/persistence/sources/build.gradle.kts b/apps/flipcash/shared/persistence/sources/build.gradle.kts index ccb56905b3..19d9c46e97 100644 --- a/apps/flipcash/shared/persistence/sources/build.gradle.kts +++ b/apps/flipcash/shared/persistence/sources/build.gradle.kts @@ -41,4 +41,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/apps/flipcash/shared/theme/build.gradle.kts b/apps/flipcash/shared/theme/build.gradle.kts index f70ab124e6..cd19d34932 100644 --- a/apps/flipcash/shared/theme/build.gradle.kts +++ b/apps/flipcash/shared/theme/build.gradle.kts @@ -1,13 +1,23 @@ plugins { - alias(libs.plugins.flipcash.android.library.compose) + alias(libs.plugins.flipcash.kmp.app.library) + alias(libs.plugins.compose.compiler) } -android { - namespace = "${Gradle.flipcashNamespace}.shared.theme" -} +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.shared.theme" + } -dependencies { - implementation(project(":ui:theme")) - api(libs.androidx.ui.tooling.preview) - debugApi(libs.androidx.ui.tooling) + sourceSets { + commonMain.dependencies { + implementation(project(":ui:theme")) + implementation(libs.jetbrains.compose.runtime) + implementation(libs.jetbrains.compose.foundation) + implementation(libs.jetbrains.compose.ui) + implementation(libs.jetbrains.compose.animation) + } + androidMain.dependencies { + api(libs.androidx.ui.tooling.preview) + } + } } diff --git a/apps/flipcash/shared/theme/src/main/kotlin/com/flipcash/app/theme/FlipcashPreview.kt b/apps/flipcash/shared/theme/src/androidMain/kotlin/com/flipcash/app/theme/FlipcashPreview.kt similarity index 100% rename from apps/flipcash/shared/theme/src/main/kotlin/com/flipcash/app/theme/FlipcashPreview.kt rename to apps/flipcash/shared/theme/src/androidMain/kotlin/com/flipcash/app/theme/FlipcashPreview.kt diff --git a/apps/flipcash/shared/theme/src/main/kotlin/com/flipcash/app/theme/FlipcashTheme.kt b/apps/flipcash/shared/theme/src/commonMain/kotlin/com/flipcash/app/theme/FlipcashTheme.kt similarity index 100% rename from apps/flipcash/shared/theme/src/main/kotlin/com/flipcash/app/theme/FlipcashTheme.kt rename to apps/flipcash/shared/theme/src/commonMain/kotlin/com/flipcash/app/theme/FlipcashTheme.kt diff --git a/apps/flipcash/shared/theme/src/main/kotlin/com/flipcash/app/theme/internal/FlipcashDesignSystem.kt b/apps/flipcash/shared/theme/src/commonMain/kotlin/com/flipcash/app/theme/internal/FlipcashDesignSystem.kt similarity index 100% rename from apps/flipcash/shared/theme/src/main/kotlin/com/flipcash/app/theme/internal/FlipcashDesignSystem.kt rename to apps/flipcash/shared/theme/src/commonMain/kotlin/com/flipcash/app/theme/internal/FlipcashDesignSystem.kt diff --git a/apps/flipcash/shared/tokens/build.gradle.kts b/apps/flipcash/shared/tokens/build.gradle.kts index 75776da4b7..34e4d3caf8 100644 --- a/apps/flipcash/shared/tokens/build.gradle.kts +++ b/apps/flipcash/shared/tokens/build.gradle.kts @@ -71,4 +71,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/apps/flipcash/shared/transaction-history/build.gradle.kts b/apps/flipcash/shared/transaction-history/build.gradle.kts index 2f0ad39f79..5e5ccc3462 100644 --- a/apps/flipcash/shared/transaction-history/build.gradle.kts +++ b/apps/flipcash/shared/transaction-history/build.gradle.kts @@ -81,4 +81,6 @@ compose.resources { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/apps/flipcash/shared/userflags/build.gradle.kts b/apps/flipcash/shared/userflags/build.gradle.kts index 365a0bfbf0..bcb6712ac2 100644 --- a/apps/flipcash/shared/userflags/build.gradle.kts +++ b/apps/flipcash/shared/userflags/build.gradle.kts @@ -42,4 +42,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f9356a8896..fec7c51eff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -185,6 +185,7 @@ hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" # Plain JVM jars: @Module/@Provides/@Binds and @InstallIn/SingletonComponent compile in a KMP commonMain. dagger = { module = "com.google.dagger:dagger", version.ref = "hilt" } hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" } +dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "hilt" } hilt-worker = { module = "androidx.hilt:hilt-work", version.ref = "hilt-jetpack" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hilt-jetpack" } @@ -198,6 +199,7 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-rx3 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx3", version.ref = "kotlinx-coroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" } diff --git a/services/flipcash/build.gradle.kts b/services/flipcash/build.gradle.kts index 97e107fbf0..d6746366fc 100644 --- a/services/flipcash/build.gradle.kts +++ b/services/flipcash/build.gradle.kts @@ -62,4 +62,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } diff --git a/services/flipcash/src/androidMain/kotlin/com/flipcash/services/inject/FlipcashChannelModule.kt b/services/flipcash/src/androidMain/kotlin/com/flipcash/services/inject/FlipcashChannelModule.kt new file mode 100644 index 0000000000..2f6a6bef79 --- /dev/null +++ b/services/flipcash/src/androidMain/kotlin/com/flipcash/services/inject/FlipcashChannelModule.kt @@ -0,0 +1,178 @@ +package com.flipcash.services.inject + +import android.content.Context +import com.flipcash.services.user.AnalyticsIdentifier +import com.mixpanel.android.mpmetrics.MixpanelAPI +import com.flipcash.services.internal.annotations.FlipcashManagedChannel +import com.flipcash.services.internal.annotations.FlipcashManagedStreamingChannel +import com.flipcash.services.internal.annotations.FlipcashProtocol +import com.flipcash.services.internal.domain.ActivityFeedMessageMapper +import com.flipcash.services.internal.domain.BlockedUserMapper +import com.flipcash.services.internal.domain.ContactMapper +import com.flipcash.services.internal.domain.ImageModerationResponseMapper +import com.flipcash.services.internal.domain.UserFlagsMapper +import com.flipcash.services.internal.domain.SocialAccountMapper +import com.flipcash.services.internal.domain.TextModerationResponseMapper +import com.flipcash.services.internal.domain.UserProfileMapper +import com.flipcash.services.internal.domain.ChatMetadataMapper +import com.flipcash.services.BlobUploader +import com.flipcash.services.ForegroundGate +import com.flipcash.services.ProcessForegroundGate +import com.flipcash.services.internal.network.HttpBlobUploader +import com.flipcash.services.internal.network.services.AccountService +import com.flipcash.services.internal.network.services.ActivityFeedService +import com.flipcash.services.internal.network.services.BlobStorageService +import com.flipcash.services.internal.network.services.BlocklistService +import com.flipcash.services.internal.network.services.ChatService +import com.flipcash.services.internal.network.services.EventStreamingService +import com.flipcash.services.internal.network.services.ChatMessagingService +import com.flipcash.services.internal.network.services.EmailVerificationService +import com.flipcash.services.internal.network.services.ContactListService +import com.flipcash.services.internal.network.services.ModerationService +import com.flipcash.services.internal.network.services.PollService +import com.flipcash.services.internal.repositories.InternalPollRepository +import com.flipcash.services.repository.PollRepository +import com.flipcash.services.internal.network.services.PhoneVerificationService +import com.flipcash.services.internal.network.services.ProfileService +import com.flipcash.services.internal.network.services.PurchaseService +import com.flipcash.services.internal.network.services.PushService +import com.flipcash.services.internal.network.services.CommunityService +import com.flipcash.services.internal.network.services.ResolverService +import com.flipcash.services.internal.network.services.ReportingService +import com.flipcash.services.internal.network.services.SettingsService +import com.flipcash.services.internal.network.services.ThirdPartyService +import com.flipcash.services.internal.repositories.InternalAccountRepository +import com.flipcash.services.internal.repositories.InternalActivityFeedRepository +import com.flipcash.services.internal.repositories.InternalBlobStorageRepository +import com.flipcash.services.internal.repositories.InternalBlocklistRepository +import com.flipcash.services.internal.repositories.InternalChatRepository +import com.flipcash.services.internal.repositories.InternalEventStreamingRepository +import com.flipcash.services.internal.repositories.InternalChatMessagingRepository +import com.flipcash.services.internal.repositories.InternalContactListRepository +import com.flipcash.services.internal.repositories.InternalContactVerificationRepository +import com.flipcash.services.internal.repositories.InternalModerationRepository +import com.flipcash.services.internal.repositories.InternalProfileRepository +import com.flipcash.services.internal.repositories.InternalPurchaseRepository +import com.flipcash.services.internal.repositories.InternalPushRepository +import com.flipcash.services.internal.repositories.InternalCommunityRepository +import com.flipcash.services.internal.repositories.InternalResolverRepository +import com.flipcash.services.internal.repositories.InternalReportingRepository +import com.flipcash.services.internal.repositories.InternalSettingsRepository +import com.flipcash.services.internal.repositories.InternalThirdPartyRepository +import com.flipcash.services.repository.AccountRepository +import com.flipcash.services.repository.ActivityFeedRepository +import com.flipcash.services.repository.BlocklistRepository +import com.flipcash.services.repository.ChatRepository +import com.flipcash.services.repository.EventStreamingRepository +import com.flipcash.services.repository.ChatMessagingRepository +import com.flipcash.services.repository.ContactListRepository +import com.flipcash.services.repository.ContactVerificationRepository +import com.flipcash.services.repository.ModerationRepository +import com.flipcash.services.repository.ProfileRepository +import com.flipcash.services.repository.PurchaseRepository +import com.flipcash.services.repository.PushRepository +import com.flipcash.services.repository.BlobStorageRepository +import com.flipcash.services.repository.CommunityRepository +import com.flipcash.services.repository.ResolverRepository +import com.flipcash.services.repository.ReportingRepository +import com.flipcash.services.repository.SettingsRepository +import com.flipcash.services.repository.ThirdPartyRepository +import com.getcode.opencode.ProtocolConfig +import com.getcode.opencode.utils.logging.LoggingClientInterceptor +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import com.getcode.utils.TraceType +import com.getcode.utils.trace +import io.grpc.ConnectivityState +import io.grpc.ManagedChannel +import io.grpc.android.AndroidChannelBuilder +import io.grpc.okhttp.OkHttpChannelBuilder +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object FlipcashChannelModule { + @Provides + fun provideAnalyticsIdentifier(mixpanelAPI: MixpanelAPI): AnalyticsIdentifier = + AnalyticsIdentifier { mixpanelAPI.identify(it) } + + @Singleton + @FlipcashProtocol + @Provides + fun providesFlipcashProtocolConfig( + @ApplicationContext context: Context + ): ProtocolConfig { + return object : ProtocolConfig { + override val baseUrl: String + get() = "fc-v2.api.flipcash-infra.net" + override val userAgent: String + get() { + val version = + context.packageManager.getPackageInfo(context.packageName, 0).versionName + return "Flipcash/Android/$version" + } + } + } + + @Singleton + @Provides + @FlipcashManagedChannel + fun provideManagedChannel( + @ApplicationContext context: Context, + @FlipcashProtocol + config: ProtocolConfig, + ): ManagedChannel { + return AndroidChannelBuilder + .usingBuilder(OkHttpChannelBuilder.forAddress(config.baseUrl, config.port)) + .context(context) + .userAgent(config.userAgent) + .keepAliveWithoutCalls(false) + .idleTimeout(5, TimeUnit.MINUTES) + .intercept(LoggingClientInterceptor()) + .build() + .also { observeChannelState("flipcash", it) } + } + + @Singleton + @Provides + @FlipcashManagedStreamingChannel + fun provideManagedStreamingChannel( + @ApplicationContext context: Context, + @FlipcashProtocol + config: ProtocolConfig, + ): ManagedChannel { + return AndroidChannelBuilder + .usingBuilder(OkHttpChannelBuilder.forAddress(config.baseUrl, config.port)) + .context(context) + .userAgent(config.userAgent) + .keepAliveTime(config.keepAlive.inWholeMilliseconds, TimeUnit.MILLISECONDS) + .keepAliveTimeout(config.keepAliveTimeout.inWholeMilliseconds, TimeUnit.MILLISECONDS) + .keepAliveWithoutCalls(true) + .intercept(LoggingClientInterceptor()) + .build() + .also { observeChannelState("flipcash-stream", it) } + } + + private fun observeChannelState(name: String, channel: ManagedChannel) { + val state = channel.getState(false) + trace( + tag = "gRPC", + message = "$name => $state", + type = TraceType.StateChange, + ) + if (state != ConnectivityState.SHUTDOWN) { + channel.notifyWhenStateChanged(state) { + observeChannelState(name, channel) + } + } + } + + @Provides + internal fun providesForegroundGate( + gate: ProcessForegroundGate, + ): ForegroundGate = gate +} diff --git a/services/flipcash/src/commonMain/kotlin/com/flipcash/services/inject/ChatCryptoModule.kt b/services/flipcash/src/commonMain/kotlin/com/flipcash/services/inject/ChatCryptoModule.kt index 76c4cc2d78..56920da6fe 100644 --- a/services/flipcash/src/commonMain/kotlin/com/flipcash/services/inject/ChatCryptoModule.kt +++ b/services/flipcash/src/commonMain/kotlin/com/flipcash/services/inject/ChatCryptoModule.kt @@ -14,13 +14,13 @@ import dagger.hilt.components.SingletonComponent @Module @InstallIn(SingletonComponent::class) -internal abstract class ChatCryptoModule { +abstract class ChatCryptoModule { @Binds - abstract fun bindChatKeySource(source: DefaultChatKeySource): ChatKeySource + internal abstract fun bindChatKeySource(source: DefaultChatKeySource): ChatKeySource @Binds - abstract fun bindGroupKeySource(source: GroupKeyEnvelopes): GroupKeySource + internal abstract fun bindGroupKeySource(source: GroupKeyEnvelopes): GroupKeySource companion object { @Provides diff --git a/services/flipcash/src/androidMain/kotlin/com/flipcash/services/inject/FlipcashModule.kt b/services/flipcash/src/commonMain/kotlin/com/flipcash/services/inject/FlipcashModule.kt similarity index 75% rename from services/flipcash/src/androidMain/kotlin/com/flipcash/services/inject/FlipcashModule.kt rename to services/flipcash/src/commonMain/kotlin/com/flipcash/services/inject/FlipcashModule.kt index e45cd2ff18..0ebc4c56f1 100644 --- a/services/flipcash/src/androidMain/kotlin/com/flipcash/services/inject/FlipcashModule.kt +++ b/services/flipcash/src/commonMain/kotlin/com/flipcash/services/inject/FlipcashModule.kt @@ -1,8 +1,6 @@ package com.flipcash.services.inject -import android.content.Context import com.flipcash.services.user.AnalyticsIdentifier -import com.mixpanel.android.mpmetrics.MixpanelAPI import com.flipcash.services.internal.annotations.FlipcashManagedChannel import com.flipcash.services.internal.annotations.FlipcashManagedStreamingChannel import com.flipcash.services.internal.annotations.FlipcashProtocol @@ -17,7 +15,6 @@ import com.flipcash.services.internal.domain.UserProfileMapper import com.flipcash.services.internal.domain.ChatMetadataMapper import com.flipcash.services.BlobUploader import com.flipcash.services.ForegroundGate -import com.flipcash.services.ProcessForegroundGate import com.flipcash.services.internal.network.HttpBlobUploader import com.flipcash.services.internal.network.services.AccountService import com.flipcash.services.internal.network.services.ActivityFeedService @@ -82,96 +79,17 @@ import com.getcode.opencode.utils.logging.LoggingClientInterceptor import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import com.getcode.utils.TraceType import com.getcode.utils.trace -import io.grpc.ConnectivityState import io.grpc.ManagedChannel -import io.grpc.android.AndroidChannelBuilder -import io.grpc.okhttp.OkHttpChannelBuilder -import java.util.concurrent.TimeUnit import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object FlipcashModule { - @Provides - fun provideAnalyticsIdentifier(mixpanelAPI: MixpanelAPI): AnalyticsIdentifier = - AnalyticsIdentifier { mixpanelAPI.identify(it) } - - @Singleton - @FlipcashProtocol - @Provides - fun providesFlipcashProtocolConfig( - @ApplicationContext context: Context - ): ProtocolConfig { - return object : ProtocolConfig { - override val baseUrl: String - get() = "fc-v2.api.flipcash-infra.net" - override val userAgent: String - get() { - val version = - context.packageManager.getPackageInfo(context.packageName, 0).versionName - return "Flipcash/Android/$version" - } - } - } - - @Singleton - @Provides - @FlipcashManagedChannel - fun provideManagedChannel( - @ApplicationContext context: Context, - @FlipcashProtocol - config: ProtocolConfig, - ): ManagedChannel { - return AndroidChannelBuilder - .usingBuilder(OkHttpChannelBuilder.forAddress(config.baseUrl, config.port)) - .context(context) - .userAgent(config.userAgent) - .keepAliveWithoutCalls(false) - .idleTimeout(5, TimeUnit.MINUTES) - .intercept(LoggingClientInterceptor()) - .build() - .also { observeChannelState("flipcash", it) } - } - - @Singleton - @Provides - @FlipcashManagedStreamingChannel - fun provideManagedStreamingChannel( - @ApplicationContext context: Context, - @FlipcashProtocol - config: ProtocolConfig, - ): ManagedChannel { - return AndroidChannelBuilder - .usingBuilder(OkHttpChannelBuilder.forAddress(config.baseUrl, config.port)) - .context(context) - .userAgent(config.userAgent) - .keepAliveTime(config.keepAlive.inWholeMilliseconds, TimeUnit.MILLISECONDS) - .keepAliveTimeout(config.keepAliveTimeout.inWholeMilliseconds, TimeUnit.MILLISECONDS) - .keepAliveWithoutCalls(true) - .intercept(LoggingClientInterceptor()) - .build() - .also { observeChannelState("flipcash-stream", it) } - } - - private fun observeChannelState(name: String, channel: ManagedChannel) { - val state = channel.getState(false) - trace( - tag = "gRPC", - message = "$name => $state", - type = TraceType.StateChange, - ) - if (state != ConnectivityState.SHUTDOWN) { - channel.notifyWhenStateChanged(state) { - observeChannelState(name, channel) - } - } - } - +object FlipcashModule { @Provides + @JvmName("providesChatRepository") internal fun providesChatRepository( service: ChatService, mapper: ChatMetadataMapper, @@ -179,22 +97,26 @@ internal object FlipcashModule { ): ChatRepository = InternalChatRepository(service, mapper, userProfileMapper) @Provides + @JvmName("providesEventStreamingRepository") internal fun providesEventStreamingRepository( service: EventStreamingService, ): EventStreamingRepository = InternalEventStreamingRepository(service) @Provides + @JvmName("providesChatMessagingRepository") internal fun providesChatMessagingRepository( service: ChatMessagingService, ): ChatMessagingRepository = InternalChatMessagingRepository(service) @Provides + @JvmName("providesContactListRepository") internal fun providesContactListRepository( service: ContactListService, contactMapper: ContactMapper, ): ContactListRepository = InternalContactListRepository(service, contactMapper) @Provides + @JvmName("providesCommunityRepository") internal fun providesCommunityRepository( service: CommunityService, chatMapper: ChatMetadataMapper, @@ -202,64 +124,70 @@ internal object FlipcashModule { ): CommunityRepository = InternalCommunityRepository(service, chatMapper, userProfileMapper) @Provides + @JvmName("providesResolverRepository") internal fun providesResolverRepository( service: ResolverService, ): ResolverRepository = InternalResolverRepository(service) @Provides + @JvmName("providesBlobStorageRepository") internal fun providesBlobStorageRepository( service: BlobStorageService, ): BlobStorageRepository = InternalBlobStorageRepository(service) @Provides + @JvmName("providesBlobUploader") internal fun providesBlobUploader( uploader: HttpBlobUploader, ): BlobUploader = uploader @Provides - internal fun providesForegroundGate( - gate: ProcessForegroundGate, - ): ForegroundGate = gate - - @Provides + @JvmName("providesAccountRepository") internal fun providesAccountRepository( service: AccountService, mapper: UserFlagsMapper, ): AccountRepository = InternalAccountRepository(service, mapper) @Provides + @JvmName("providesActivityFeedRepository") internal fun providesActivityFeedRepository( service: ActivityFeedService, mapper: ActivityFeedMessageMapper, ): ActivityFeedRepository = InternalActivityFeedRepository(service, mapper) @Provides + @JvmName("providesBlocklistRepository") internal fun providesBlocklistRepository( service: BlocklistService, mapper: BlockedUserMapper, ): BlocklistRepository = InternalBlocklistRepository(service, mapper) @Provides + @JvmName("providesPurchaseRepository") internal fun providesPurchaseRepository( service: PurchaseService, ): PurchaseRepository = InternalPurchaseRepository(service) @Provides + @JvmName("providesPushRepository") internal fun providesPushRepository( service: PushService, ): PushRepository = InternalPushRepository(service) @Provides + @JvmName("providesSettingsRepository") internal fun providesSettingsRepository( service: SettingsService ): SettingsRepository = InternalSettingsRepository(service) @Provides + @JvmName("providesThirdPartyRepository") internal fun providesThirdPartyRepository( service: ThirdPartyService, ): ThirdPartyRepository = InternalThirdPartyRepository(service) @Provides + @JvmName("providesContactVerificationRepository") internal fun providesContactVerificationRepository( emailService: EmailVerificationService, phoneService: PhoneVerificationService, @@ -267,6 +195,7 @@ internal object FlipcashModule { InternalContactVerificationRepository(emailService, phoneService) @Provides + @JvmName("providesProfileRepository") internal fun providesProfileRepository( service: ProfileService, userProfileMapper: UserProfileMapper, @@ -275,11 +204,13 @@ internal object FlipcashModule { InternalProfileRepository(service, userProfileMapper, socialAccountMapper) @Provides + @JvmName("providesPollRepository") internal fun providesPollRepository( service: PollService, ): PollRepository = InternalPollRepository(service) @Provides + @JvmName("providesModerationRepository") internal fun providesModerationRepository( service: ModerationService, textModerationResponseMapper: TextModerationResponseMapper, @@ -291,7 +222,8 @@ internal object FlipcashModule { ) @Provides + @JvmName("providesReportingRepository") internal fun providesReportingRepository( service: ReportingService, ): ReportingRepository = InternalReportingRepository(service) -} \ No newline at end of file +} diff --git a/services/opencode/build.gradle.kts b/services/opencode/build.gradle.kts index bccc54869c..30b8bbfb25 100644 --- a/services/opencode/build.gradle.kts +++ b/services/opencode/build.gradle.kts @@ -80,6 +80,8 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } // The headless balance test reads its recovery phrase from local.properties (gitignored) or the diff --git a/services/opencode/src/androidMain/kotlin/com/getcode/opencode/RepositoryFactory.kt b/services/opencode/src/androidMain/kotlin/com/getcode/opencode/RepositoryFactory.kt index 4d0fa240ea..4502e51c25 100644 --- a/services/opencode/src/androidMain/kotlin/com/getcode/opencode/RepositoryFactory.kt +++ b/services/opencode/src/androidMain/kotlin/com/getcode/opencode/RepositoryFactory.kt @@ -1,6 +1,7 @@ package com.getcode.opencode import android.content.Context +import com.getcode.opencode.inject.OpenCodeChannelModule import com.getcode.opencode.inject.OpenCodeModule import com.getcode.opencode.internal.domain.mapping.BillCustomizationMapper import com.getcode.opencode.internal.domain.mapping.HistoricalMintDataMapper @@ -43,8 +44,8 @@ object RepositoryFactory { OpenCodeModule::class.java, ) - val api = AccountApi(module.provideManagedChannel(context, config)) - val transactionApi = TransactionApi(module.provideManagedChannel(context, config)) + val api = AccountApi(OpenCodeChannelModule.provideManagedChannel(context, config)) + val transactionApi = TransactionApi(OpenCodeChannelModule.provideManagedChannel(context, config)) val service = AccountService(api) val transactionMetadataMapper = TransactionMetadataMapper() val swapFunding = SwapFunding(transactionApi) @@ -86,7 +87,7 @@ object RepositoryFactory { OpenCodeModule::class.java, ) - val api = MessagingApi(module.provideManagedChannel(context, config)) + val api = MessagingApi(OpenCodeChannelModule.provideManagedChannel(context, config)) val service = MessagingService(api) return module.providesMessagingRepository(service) } @@ -101,7 +102,7 @@ object RepositoryFactory { OpenCodeModule::class.java, ) - val api = TransactionApi(module.provideManagedChannel(context, config)) + val api = TransactionApi(OpenCodeChannelModule.provideManagedChannel(context, config)) val transactionMetadataMapper = TransactionMetadataMapper() val swapFunding = SwapFunding(api) val service = TransactionService(api, transactionMetadataMapper, swapFunding) @@ -118,7 +119,7 @@ object RepositoryFactory { OpenCodeModule::class.java, ) - val api = TransactionApi(module.provideManagedChannel(context, config)) + val api = TransactionApi(OpenCodeChannelModule.provideManagedChannel(context, config)) val swapMetadataMapper = SwapMetadataMapper() val service = SwapService(api, swapMetadataMapper) val poller = SwapPoller(service) @@ -139,8 +140,8 @@ object RepositoryFactory { ) val api = CurrencyApi( - managedChannel = module.provideManagedChannel(context, config), - streamingChannel = module.provideManagedStreamingChannel(context, config) + managedChannel = OpenCodeChannelModule.provideManagedChannel(context, config), + streamingChannel = OpenCodeChannelModule.provideManagedStreamingChannel(context, config) ) val mintMapper = MintMapper( vmMetadataMapper = VmMetadataMapper(), diff --git a/services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/OpenCodeChannelModule.kt b/services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/OpenCodeChannelModule.kt new file mode 100644 index 0000000000..2f714dc84b --- /dev/null +++ b/services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/OpenCodeChannelModule.kt @@ -0,0 +1,93 @@ +package com.getcode.opencode.inject + +import android.content.Context +import com.getcode.opencode.ProtocolConfig +import com.getcode.opencode.internal.annotations.OpenCodeManagedChannel +import com.getcode.opencode.internal.annotations.OpenCodeManagedStreamingChannel +import com.getcode.opencode.internal.annotations.OpenCodeProtocol +import com.getcode.opencode.utils.logging.LoggingClientInterceptor +import com.getcode.utils.TraceType +import com.getcode.utils.trace +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import io.grpc.ConnectivityState +import io.grpc.ManagedChannel +import io.grpc.android.AndroidChannelBuilder +import io.grpc.okhttp.OkHttpChannelBuilder +import java.util.concurrent.TimeUnit +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +object OpenCodeChannelModule { + @Singleton + @OpenCodeProtocol + @Provides + fun providesOpenCodeProtocolConfig( + @ApplicationContext context: Context + ): ProtocolConfig { + return object: ProtocolConfig { + override val baseUrl: String + get() = "ocp-v2.api.flipcash-infra.net" + override val userAgent: String + get() { + val version = context.packageManager.getPackageInfo(context.packageName, 0).versionName + return "OpenCodeProtocol/Android/$version" + } + } + } + + @Singleton + @Provides + @OpenCodeManagedChannel + fun provideManagedChannel( + @ApplicationContext context: Context, + @OpenCodeProtocol + config: ProtocolConfig, + ): ManagedChannel { + return AndroidChannelBuilder + .usingBuilder(OkHttpChannelBuilder.forAddress(config.baseUrl, config.port)) + .context(context) + .userAgent(config.userAgent) + .keepAliveWithoutCalls(false) + .idleTimeout(5, TimeUnit.MINUTES) // drop idle connections + .intercept(LoggingClientInterceptor()) + .build() + .also { observeChannelState("opencode", it) } + } + + @Singleton + @Provides + @OpenCodeManagedStreamingChannel + fun provideManagedStreamingChannel( + @ApplicationContext context: Context, + @OpenCodeProtocol + config: ProtocolConfig, + ): ManagedChannel { + return AndroidChannelBuilder + .usingBuilder(OkHttpChannelBuilder.forAddress(config.baseUrl, config.port)) + .context(context) + .userAgent(config.userAgent) + .keepAliveWithoutCalls(true) + .intercept(LoggingClientInterceptor()) + .build() + .also { observeChannelState("opencode-stream", it) } + } + + private fun observeChannelState(name: String, channel: ManagedChannel) { + val state = channel.getState(false) + trace( + tag = "gRPC", + message = "$name => $state", + type = TraceType.StateChange, + ) + if (state != ConnectivityState.SHUTDOWN) { + channel.notifyWhenStateChanged(state) { + observeChannelState(name, channel) + } + } + } +} diff --git a/services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/OpenCodeModule.kt b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/inject/OpenCodeModule.kt similarity index 62% rename from services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/OpenCodeModule.kt rename to services/opencode/src/commonMain/kotlin/com/getcode/opencode/inject/OpenCodeModule.kt index b6a6b33081..8a1b74de94 100644 --- a/services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/OpenCodeModule.kt +++ b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/inject/OpenCodeModule.kt @@ -1,7 +1,5 @@ package com.getcode.opencode.inject -import android.content.Context -import com.getcode.opencode.ProtocolConfig import com.getcode.opencode.controllers.AccountController import com.getcode.opencode.controllers.CurrencyController import com.getcode.opencode.controllers.TokenController @@ -9,9 +7,6 @@ import com.getcode.opencode.controllers.TransactionController import com.getcode.opencode.controllers.TransactionOperations import com.getcode.opencode.exchange.Exchange import com.getcode.opencode.exchange.VerifiedFiatCalculator -import com.getcode.opencode.internal.annotations.OpenCodeManagedChannel -import com.getcode.opencode.internal.annotations.OpenCodeManagedStreamingChannel -import com.getcode.opencode.internal.annotations.OpenCodeProtocol import com.getcode.opencode.internal.domain.repositories.InternalAccountRepository import com.getcode.opencode.internal.domain.repositories.InternalBalanceRepository import com.getcode.opencode.internal.domain.repositories.InternalCurrencyRepository @@ -37,22 +32,13 @@ import com.getcode.opencode.repositories.EventRepository import com.getcode.opencode.repositories.MessagingRepository import com.getcode.opencode.repositories.SwapRepository import com.getcode.opencode.repositories.TransactionRepository -import com.getcode.opencode.utils.logging.LoggingClientInterceptor import com.getcode.util.locale.LocaleHelper import com.getcode.util.resources.ResourceHelper import com.hoc081098.channeleventbus.ChannelEventBus import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import com.getcode.utils.TraceType -import com.getcode.utils.trace -import io.grpc.ConnectivityState -import io.grpc.ManagedChannel -import io.grpc.android.AndroidChannelBuilder -import io.grpc.okhttp.OkHttpChannelBuilder -import java.util.concurrent.TimeUnit import javax.inject.Singleton import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -62,6 +48,7 @@ import kotlin.time.Duration.Companion.seconds object OpenCodeModule { @Provides @Singleton + @JvmName("providesExchange") internal fun providesExchange( currencyController: CurrencyController, verifiedStateManager: VerifiedProtoManager, @@ -74,76 +61,10 @@ object OpenCodeModule { locale = locale, ) - @Singleton - @OpenCodeProtocol - @Provides - fun providesOpenCodeProtocolConfig( - @ApplicationContext context: Context - ): ProtocolConfig { - return object: ProtocolConfig { - override val baseUrl: String - get() = "ocp-v2.api.flipcash-infra.net" - override val userAgent: String - get() { - val version = context.packageManager.getPackageInfo(context.packageName, 0).versionName - return "OpenCodeProtocol/Android/$version" - } - } - } - - @Singleton - @Provides - @OpenCodeManagedChannel - fun provideManagedChannel( - @ApplicationContext context: Context, - @OpenCodeProtocol - config: ProtocolConfig, - ): ManagedChannel { - return AndroidChannelBuilder - .usingBuilder(OkHttpChannelBuilder.forAddress(config.baseUrl, config.port)) - .context(context) - .userAgent(config.userAgent) - .keepAliveWithoutCalls(false) - .idleTimeout(5, TimeUnit.MINUTES) // drop idle connections - .intercept(LoggingClientInterceptor()) - .build() - .also { observeChannelState("opencode", it) } - } - - @Singleton - @Provides - @OpenCodeManagedStreamingChannel - fun provideManagedStreamingChannel( - @ApplicationContext context: Context, - @OpenCodeProtocol - config: ProtocolConfig, - ): ManagedChannel { - return AndroidChannelBuilder - .usingBuilder(OkHttpChannelBuilder.forAddress(config.baseUrl, config.port)) - .context(context) - .userAgent(config.userAgent) - .keepAliveWithoutCalls(true) - .intercept(LoggingClientInterceptor()) - .build() - .also { observeChannelState("opencode-stream", it) } - } - - private fun observeChannelState(name: String, channel: ManagedChannel) { - val state = channel.getState(false) - trace( - tag = "gRPC", - message = "$name => $state", - type = TraceType.StateChange, - ) - if (state != ConnectivityState.SHUTDOWN) { - channel.notifyWhenStateChanged(state) { - observeChannelState(name, channel) - } - } - } @Provides @Singleton + @JvmName("providesAccountRepository") internal fun providesAccountRepository( service: AccountService, transactionService: TransactionService, @@ -151,18 +72,21 @@ object OpenCodeModule { @Provides @Singleton + @JvmName("providesMessagingRepository") internal fun providesMessagingRepository( service: MessagingService ): MessagingRepository = InternalMessagingRepository(service) @Provides @Singleton + @JvmName("providesTransactionRepository") internal fun providesTransactionRepository( service: TransactionService, ): TransactionRepository = InternalTransactionRepository(service) @Provides @Singleton + @JvmName("providesSwapRepository") internal fun providesSwapRepository( service: SwapService, swapPoller: SwapPoller, @@ -170,6 +94,7 @@ object OpenCodeModule { @Provides @Singleton + @JvmName("providesEventRepository") internal fun providesEventRepository( eventBus: ChannelEventBus, accountController: AccountController, @@ -179,18 +104,21 @@ object OpenCodeModule { @Provides @Singleton + @JvmName("providesCurrencyRepository") internal fun providesCurrencyRepository( service: CurrencyService ): CurrencyRepository = InternalCurrencyRepository(service) @Provides @Singleton + @JvmName("providesBalanceRepository") internal fun providesBalanceRepository( service: BalanceService ): BalanceRepository = InternalBalanceRepository(service) @Provides @Singleton + @JvmName("providesEventBus") internal fun providesEventBus(): ChannelEventBus = ChannelEventBus() @Provides @@ -199,5 +127,6 @@ object OpenCodeModule { @Provides @Singleton + @JvmName("bindVerifiedFiatCalculator") internal fun bindVerifiedFiatCalculator(impl: RealVerifiedFiatCalculator): VerifiedFiatCalculator = impl } \ No newline at end of file diff --git a/services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/SessionListenersModule.kt b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/inject/SessionListenersModule.kt similarity index 100% rename from services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/SessionListenersModule.kt rename to services/opencode/src/commonMain/kotlin/com/getcode/opencode/inject/SessionListenersModule.kt diff --git a/services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/TransactorModule.kt b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/inject/TransactorModule.kt similarity index 100% rename from services/opencode/src/androidMain/kotlin/com/getcode/opencode/inject/TransactorModule.kt rename to services/opencode/src/commonMain/kotlin/com/getcode/opencode/inject/TransactorModule.kt diff --git a/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/annotations/OpenCodeManagedChannel.kt b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/annotations/OpenCodeManagedChannel.kt index 00a4dfd2bb..6dabebc73d 100644 --- a/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/annotations/OpenCodeManagedChannel.kt +++ b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/annotations/OpenCodeManagedChannel.kt @@ -10,7 +10,7 @@ import javax.inject.Qualifier AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD ) -internal annotation class OpenCodeManagedChannel +annotation class OpenCodeManagedChannel @Qualifier @@ -21,4 +21,4 @@ internal annotation class OpenCodeManagedChannel AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD ) -internal annotation class OpenCodeManagedStreamingChannel \ No newline at end of file +annotation class OpenCodeManagedStreamingChannel \ No newline at end of file diff --git a/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/annotations/Protocol.kt b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/annotations/Protocol.kt index bbb968bbc9..8437d347ed 100644 --- a/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/annotations/Protocol.kt +++ b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/annotations/Protocol.kt @@ -11,4 +11,4 @@ import javax.inject.Qualifier AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD ) -internal annotation class OpenCodeProtocol \ No newline at end of file +annotation class OpenCodeProtocol \ No newline at end of file diff --git a/ui/navigation/build.gradle.kts b/ui/navigation/build.gradle.kts index b124afb989..c658b24e56 100644 --- a/ui/navigation/build.gradle.kts +++ b/ui/navigation/build.gradle.kts @@ -52,4 +52,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } From c9ef6dc5d80c3ef17de569604eecbcb7b05debdb Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 23 Oct 2026 19:55:15 -0400 Subject: [PATCH 15/17] feat(desktop): sign in with a dev phrase and show the live balance DevSession reads FLIPCASH_DEV_MNEMONIC, which Gradle sets for the desktop JavaExec tasks only when local.properties has flipcash.test.mnemonic. It goes in the environment rather than a -D flag so the phrase stays out of the process command line, and it is never logged. Four gaps kept the wallet on its loading state after sign-in: - Nothing constructed EventRepository, which turns Events.OnLoggedIn into SessionListener.onUserLoggedIn. Android builds it eagerly in FlipcashApp; the graph now exposes it and devSignIn touches it first. - TokenCoordinator joins the listener set in TokenModule, which is Android-only. DesktopModule now adds it. - Through Dagger's generated factories, Metro 1.4.5 drops provider-level @Singleton, so each injection site got its own ChannelEventBus and events never reached the repository. services:opencode now applies Metro with Dagger interop and generates its own factories instead of running dagger-compiler on jvm. - The bonding curve tables were never loaded, so every balance threw "DiscreteBondingCurve not initialized" inside a retry that swallowed it. devSignIn calls Curves.initialize() first, as Android's Application does. OpenCodeExchange's state fields move above init: init hands streamRates to another thread, which could read them before they were assigned. --- apps/desktop/build.gradle.kts | 9 ++++ .../com/flipcash/desktop/DesktopGraph.kt | 6 +++ .../com/flipcash/desktop/DesktopModule.kt | 11 +++- .../kotlin/com/flipcash/desktop/DevSession.kt | 50 +++++++++++++++++++ .../main/kotlin/com/flipcash/desktop/Main.kt | 2 + .../com/flipcash/desktop/SnapshotMain.kt | 2 + services/opencode/build.gradle.kts | 12 ++++- .../internal/exchange/OpenCodeExchange.kt | 12 ++--- 8 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt diff --git a/apps/desktop/build.gradle.kts b/apps/desktop/build.gradle.kts index f0dd564dc9..fd050df399 100644 --- a/apps/desktop/build.gradle.kts +++ b/apps/desktop/build.gradle.kts @@ -47,6 +47,11 @@ dependencies { implementation(libs.grpc.okhttp) } +// Dev-only sign-in phrase (see DevSession), forwarded only when local.properties has one. It goes in +// the environment, not a -D flag, so it stays out of the process's command line. +val devMnemonic = tryReadProperty(rootProject.rootDir, "flipcash.test.mnemonic", "") + .takeIf { it.isNotBlank() } + compose.desktop { application { mainClass = "com.flipcash.desktop.MainKt" @@ -65,3 +70,7 @@ tasks.register("snapshot") { mainClass.set("com.flipcash.desktop.SnapshotMainKt") args(providers.gradleProperty("out").getOrElse(layout.buildDirectory.file("wallet.png").get().asFile.path)) } + +tasks.withType().configureEach { + devMnemonic?.let { environment("FLIPCASH_DEV_MNEMONIC", it) } +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt index 91c09bb3ba..686beba4e4 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt @@ -37,4 +37,10 @@ interface DesktopGraph { val resources: ResourceHelper val networkObserver: NetworkConnectivityListener val analytics: FlipcashAnalytics + val userManager: com.flipcash.services.user.UserManager + val accountController: com.flipcash.services.controllers.AccountController + + // Android constructs this eagerly in FlipcashApp.onCreate; it is what turns Events.OnLoggedIn into + // SessionListener.onUserLoggedIn calls. + val eventRepository: com.getcode.opencode.repositories.EventRepository } diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt index ee09e944c0..f984eb8a38 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt @@ -16,6 +16,7 @@ import com.flipcash.app.userflags.UserFlagsFiles import com.flipcash.libs.coroutines.DispatcherProvider import com.getcode.chatcipher.ChatCipher import com.getcode.chatcipher.DefaultChatCipher +import com.getcode.opencode.providers.SessionListener import com.getcode.opencode.providers.TokenMetadataProvider import com.getcode.util.locale.LocaleHelper import com.getcode.util.resources.ComposeResourceHelper @@ -24,6 +25,7 @@ import com.getcode.utils.network.NetworkConnectivityListener import com.getcode.utils.network.NetworkObserverStub import dagger.Module import dagger.Provides +import dagger.multibindings.IntoSet import java.io.File import java.util.Currency import java.util.Locale @@ -42,7 +44,8 @@ import kotlinx.coroutines.flow.map */ @Module object DesktopModule { - private val appDir: File by lazy { + /** Per-user data directory: the database, DataStore files and no-backup files. */ + internal val appDir: File by lazy { File(System.getProperty("user.home"), ".flipcash").apply { mkdirs() } } @@ -99,6 +102,12 @@ object DesktopModule { @Provides fun tokenMetadata(coordinator: TokenCoordinator): TokenMetadataProvider = coordinator + // Android adds TokenCoordinator to the listener set in TokenModule, which is Android-only. Without this + // the coordinator never sees onUserLoggedIn and the wallet stays on its loading state. + @Provides + @IntoSet + fun tokenSessionListener(coordinator: TokenCoordinator): SessionListener = coordinator + @Provides @Singleton fun purchaseMethods(): PurchaseMethodController = object : PurchaseMethodController { diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt new file mode 100644 index 0000000000..94b45960b3 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt @@ -0,0 +1,50 @@ +package com.flipcash.desktop + +import com.flipcash.app.persistence.FlipcashDatabase +import com.flipcash.app.persistence.init +import com.flipcash.libs.currency.math.Curves +import com.flipcash.libs.currency.math.initialize +import com.flipcash.services.controllers.AccountController +import com.flipcash.services.user.AuthState +import com.flipcash.services.user.UserManager +import com.getcode.crypt.MnemonicPhrase +import java.io.File + +/** + * Development-only sign-in until the link-from-phone flow lands: reads a recovery phrase from the + * `FLIPCASH_DEV_MNEMONIC` environment variable (set by Gradle from `flipcash.test.mnemonic` in + * local.properties, only when present) and walks the same steps `AuthManager.login` does for an + * account that has finished onboarding. The phrase is never logged. + */ +internal object DevSession { + private const val VARIABLE = "FLIPCASH_DEV_MNEMONIC" + + suspend fun signIn( + userManager: UserManager, + accountController: AccountController, + dataDir: File, + ): Result { + val words = System.getenv(VARIABLE)?.trim()?.split(Regex("\\s+")) + ?.takeIf { it.size > 1 } + ?: return Result.failure(IllegalStateException("$VARIABLE not set")) + val entropy = MnemonicPhrase.newInstance(words)?.getBase64EncodedEntropy() + ?: return Result.failure(IllegalArgumentException("$VARIABLE is not a 12 or 24 word phrase")) + + userManager.establish(entropy) + return accountController.login().map { accountId -> + FlipcashDatabase.init(dataDir, entropy) + userManager.set(accountId = accountId) + userManager.set(AuthState.Ready) + } + } +} + +/** Signs in with the dev phrase when one is configured; otherwise the wallet stays signed out. */ +internal suspend fun DesktopGraph.devSignIn() { + // Android loads the curve tables in its Application; balances throw without them. + Curves.initialize() + eventRepository // must exist before login so OnLoggedIn reaches the session listeners + DevSession.signIn(userManager, accountController, DesktopModule.appDir) + .onSuccess { println("dev session: signed in") } + .onFailure { println("dev session: not signed in (${it.message})") } +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt index a56821ba1e..f8c5e95ba2 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt @@ -6,10 +6,12 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import dev.zacsweers.metro.createGraph +import kotlinx.coroutines.runBlocking import java.lang.management.ManagementFactory fun main() { val graph = createGraph() + runBlocking { graph.devSignIn() } application { Window( onCloseRequest = ::exitApplication, diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt index c3fd176163..f93bd85cb0 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt @@ -4,6 +4,7 @@ import androidx.compose.ui.ImageComposeScene import androidx.compose.ui.unit.Density import androidx.compose.ui.use import dev.zacsweers.metro.createGraph +import kotlinx.coroutines.runBlocking import java.io.File import kotlin.system.exitProcess import org.jetbrains.skia.EncodedImageFormat @@ -15,6 +16,7 @@ import org.jetbrains.skia.EncodedImageFormat fun main(args: Array) { val out = File(args.firstOrNull() ?: "wallet.png") val graph = createGraph() + runBlocking { graph.devSignIn() } ImageComposeScene(width = 840, height = 1720, density = Density(2f)) { DesktopWalletContent(graph) }.use { scene -> diff --git a/services/opencode/build.gradle.kts b/services/opencode/build.gradle.kts index 30b8bbfb25..abdd630019 100644 --- a/services/opencode/build.gradle.kts +++ b/services/opencode/build.gradle.kts @@ -2,6 +2,17 @@ plugins { alias(libs.plugins.flipcash.kmp.app.library) alias(libs.plugins.kotlin.ksp) id("org.jetbrains.kotlin.plugin.parcelize") + alias(libs.plugins.metro) +} + +// Metro generates this module's provider factories for the desktop graph. Through Dagger's factories +// instead, Metro 1.4.5 drops provider-level @Singleton (it does not read @ScopeMetadata), so each +// injection site got its own ChannelEventBus. +metro { + interop { + includeJavax() + includeDagger() + } } kotlin { @@ -80,7 +91,6 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.compiler) - add("kspJvm", libs.dagger.compiler) add("jvmMainImplementation", libs.dagger) } diff --git a/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/exchange/OpenCodeExchange.kt b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/exchange/OpenCodeExchange.kt index 2a41088692..8ab1a82e63 100644 --- a/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/exchange/OpenCodeExchange.kt +++ b/services/opencode/src/commonMain/kotlin/com/getcode/opencode/internal/exchange/OpenCodeExchange.kt @@ -37,6 +37,12 @@ internal class OpenCodeExchange @Inject constructor( private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + // Declared ahead of init: it hands streamRates to another thread and launches a write to + // preferredCurrency, and either can run before initializers further down have. + private val _preferredRate = MutableStateFlow(Rate.oneToOne) + private val mints = MutableStateFlow>(emptyList()) + private var preferredCurrency: CurrencyCode? = null + init { // Deferred, not registered in place: this singleton is built off the main thread so its // construction stays off the startup path, and its fields below are not yet initialized. @@ -51,15 +57,11 @@ internal class OpenCodeExchange @Inject constructor( } } - - private val _preferredRate = MutableStateFlow(Rate.oneToOne) override val preferredRate: Rate get() = _preferredRate.value override fun observePreferredRate(): Flow = _preferredRate - private val mints = MutableStateFlow>(emptyList()) - override suspend fun setPreferredCurrency(currencyCode: CurrencyCode) { preferredCurrency = currencyCode verifiedStateManager.rateFor(currencyCode)?.let { @@ -73,8 +75,6 @@ internal class OpenCodeExchange @Inject constructor( this.mints.value = mints } - private var preferredCurrency: CurrencyCode? = null - override fun rates() = verifiedStateManager.rates() override fun observeRates(): Flow> = verifiedStateManager.observeRates() From d5856835d3ee5257939a516e36e8d53f94f08dd0 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 24 Oct 2026 15:17:29 -0400 Subject: [PATCH 16/17] fix(desktop): add Coil's OkHttp network fetcher so token logos load Token logos are https URLs. Coil 3 ships no network fetcher, and the desktop app had no coil-network artifact on its classpath, so every logo failed with "Unable to create a fetcher" and the card stack showed blank avatars. coil-network-okhttp registers its fetcher through ServiceLoader, so adding the dependency is the fix. installImageLoader mirrors FlipcashApp.newImageLoader (crossfade, memory cache, 50 MB disk cache), minus the chat photo keyer and fetcher. --- apps/desktop/build.gradle.kts | 2 ++ .../flipcash/desktop/DesktopImageLoader.kt | 32 +++++++++++++++++++ .../main/kotlin/com/flipcash/desktop/Main.kt | 1 + .../com/flipcash/desktop/SnapshotMain.kt | 1 + 4 files changed, 36 insertions(+) create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopImageLoader.kt diff --git a/apps/desktop/build.gradle.kts b/apps/desktop/build.gradle.kts index fd050df399..848bb43d71 100644 --- a/apps/desktop/build.gradle.kts +++ b/apps/desktop/build.gradle.kts @@ -45,6 +45,8 @@ dependencies { implementation(libs.kotlinx.coroutines.swing) implementation(libs.dagger) implementation(libs.grpc.okhttp) + implementation(libs.coil3) + implementation(libs.coil3.network) } // Dev-only sign-in phrase (see DevSession), forwarded only when local.properties has one. It goes in diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopImageLoader.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopImageLoader.kt new file mode 100644 index 0000000000..0c146a860d --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopImageLoader.kt @@ -0,0 +1,32 @@ +package com.flipcash.desktop + +import coil3.ImageLoader +import coil3.PlatformContext +import coil3.SingletonImageLoader +import coil3.disk.DiskCache +import coil3.network.okhttp.OkHttpNetworkFetcherFactory +import coil3.request.CachePolicy +import coil3.request.crossfade +import okio.Path.Companion.toOkioPath + +/** + * Desktop counterpart of `FlipcashApp.newImageLoader`. Coil 3 has no network fetcher unless one is + * added, so without this every token logo (an https URL) fails with "Unable to create a fetcher". + * The chat photo keyer/fetcher are left out: chat is not on desktop. + */ +internal fun installImageLoader() { + SingletonImageLoader.setSafe { context -> newImageLoader(context) } +} + +private fun newImageLoader(context: PlatformContext): ImageLoader = + ImageLoader.Builder(context) + .crossfade(true) + .memoryCachePolicy(CachePolicy.ENABLED) + .diskCache { + DiskCache.Builder() + .directory(DesktopModule.appDir.resolve("image_cache").toOkioPath()) + .maxSizeBytes(50L * 1024 * 1024) + .build() + } + .components { add(OkHttpNetworkFetcherFactory()) } + .build() diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt index f8c5e95ba2..9fa12ad208 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.runBlocking import java.lang.management.ManagementFactory fun main() { + installImageLoader() val graph = createGraph() runBlocking { graph.devSignIn() } application { diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt index f93bd85cb0..ad6a2caccc 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt @@ -15,6 +15,7 @@ import org.jetbrains.skia.EncodedImageFormat */ fun main(args: Array) { val out = File(args.firstOrNull() ?: "wallet.png") + installImageLoader() val graph = createGraph() runBlocking { graph.devSignIn() } ImageComposeScene(width = 840, height = 1720, density = Density(2f)) { From 7a7b785e1bf8b293ae4a9969fca4f7c77b3b0550 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sat, 24 Oct 2026 15:30:25 -0400 Subject: [PATCH 17/17] test(desktop): check desktop wiring against Android's in a shared contract Each wiring gap in A2 (the token coordinator's listener binding, the eager event repository, the curve tables, the network image loader) left the desktop wallet on a spinner or blank logos with no error. :apps:flipcash:startup-contract lists the SessionListeners desktop must bind, the ones only Android binds, and every Android startup entry point, each marked as mirrored on desktop (with a Step) or not, with a reason. - AndroidWiringTest builds the real Hilt graph under Robolectric and fails when a SessionListener or startup entry point (manifest initializer, FlipcashApp injection, SingletonImageLoader.Factory) is missing from the contract. ApiModule is replaced because MixpanelAPI.getInstance returns null under Robolectric. - DesktopWiringTest builds the real Metro graph, runs DesktopGraph.startup(), and checks the effect of each mirrored step, the listener set, and that scoped bindings resolve to one instance. The desktop startup steps move out of devSignIn into startup() so the test can run them without a sign-in phrase. Tests set opencode.processLifecycle.autoStart=false so building the exchange does not open its rate stream to the backend. --- apps/desktop/build.gradle.kts | 10 ++ .../com/flipcash/desktop/DesktopGraph.kt | 5 + .../com/flipcash/desktop/DesktopStartup.kt | 18 +++ .../kotlin/com/flipcash/desktop/DevSession.kt | 7 +- .../main/kotlin/com/flipcash/desktop/Main.kt | 6 +- .../com/flipcash/desktop/SnapshotMain.kt | 6 +- .../com/flipcash/desktop/DesktopWiringTest.kt | 115 ++++++++++++++++++ apps/flipcash/app/build.gradle.kts | 1 + .../com/flipcash/app/AndroidWiringTest.kt | 105 ++++++++++++++++ .../startup-contract/build.gradle.kts | 10 ++ .../app/startup/contract/StartupContract.kt | 87 +++++++++++++ .../internal/platform/ProcessLifecycle.jvm.kt | 7 ++ settings.gradle.kts | 1 + 13 files changed, 368 insertions(+), 10 deletions(-) create mode 100644 apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopStartup.kt create mode 100644 apps/desktop/src/test/kotlin/com/flipcash/desktop/DesktopWiringTest.kt create mode 100644 apps/flipcash/app/src/test/kotlin/com/flipcash/app/AndroidWiringTest.kt create mode 100644 apps/flipcash/startup-contract/build.gradle.kts create mode 100644 apps/flipcash/startup-contract/src/commonMain/kotlin/com/flipcash/app/startup/contract/StartupContract.kt diff --git a/apps/desktop/build.gradle.kts b/apps/desktop/build.gradle.kts index 848bb43d71..9fd564a8f6 100644 --- a/apps/desktop/build.gradle.kts +++ b/apps/desktop/build.gradle.kts @@ -47,6 +47,16 @@ dependencies { implementation(libs.grpc.okhttp) implementation(libs.coil3) implementation(libs.coil3.network) + implementation(libs.event.bus) + + testImplementation(project(":apps:flipcash:startup-contract")) + testImplementation(libs.kotlin.test.junit) + testImplementation(libs.kotlinx.coroutines.test) +} + +tasks.withType().configureEach { + // No backend in tests: keep the exchange's rate stream from starting when the graph builds it. + systemProperty("opencode.processLifecycle.autoStart", "false") } // Dev-only sign-in phrase (see DevSession), forwarded only when local.properties has one. It goes in diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt index 686beba4e4..42fa4573bd 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt @@ -43,4 +43,9 @@ interface DesktopGraph { // Android constructs this eagerly in FlipcashApp.onCreate; it is what turns Events.OnLoggedIn into // SessionListener.onUserLoggedIn calls. val eventRepository: com.getcode.opencode.repositories.EventRepository + + // For DesktopWiringTest: the listener set, and singletons to check are shared across injection sites. + val sessionListeners: Set + val tokenCoordinator: com.flipcash.app.tokens.TokenCoordinator + val eventBus: com.hoc081098.channeleventbus.ChannelEventBus } diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopStartup.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopStartup.kt new file mode 100644 index 0000000000..e4edde761b --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopStartup.kt @@ -0,0 +1,18 @@ +package com.flipcash.desktop + +import com.flipcash.libs.currency.math.Curves +import com.flipcash.libs.currency.math.initialize + +/** + * The startup work Android does in its androidx-startup initializers and `FlipcashApp.onCreate` + * that the balance path relies on. `StartupContract` lists each step; `DesktopWiringTest` checks + * that each took effect. + */ +internal suspend fun DesktopGraph.startup() { + // NetworkImageLoader: token logos are https URLs, and Coil 3 has no network fetcher by default. + installImageLoader() + // CurveTables: balance valuations throw without them. + Curves.initialize() + // EventRepository: must exist before login, or OnLoggedIn reaches no session listener. + eventRepository +} diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt index 94b45960b3..5663043352 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt @@ -2,8 +2,6 @@ package com.flipcash.desktop import com.flipcash.app.persistence.FlipcashDatabase import com.flipcash.app.persistence.init -import com.flipcash.libs.currency.math.Curves -import com.flipcash.libs.currency.math.initialize import com.flipcash.services.controllers.AccountController import com.flipcash.services.user.AuthState import com.flipcash.services.user.UserManager @@ -39,11 +37,8 @@ internal object DevSession { } } -/** Signs in with the dev phrase when one is configured; otherwise the wallet stays signed out. */ +/** Signs in with the dev phrase when one is configured; otherwise the wallet stays signed out. Run [startup] first. */ internal suspend fun DesktopGraph.devSignIn() { - // Android loads the curve tables in its Application; balances throw without them. - Curves.initialize() - eventRepository // must exist before login so OnLoggedIn reaches the session listeners DevSession.signIn(userManager, accountController, DesktopModule.appDir) .onSuccess { println("dev session: signed in") } .onFailure { println("dev session: not signed in (${it.message})") } diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt index 9fa12ad208..df61615149 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt @@ -10,9 +10,11 @@ import kotlinx.coroutines.runBlocking import java.lang.management.ManagementFactory fun main() { - installImageLoader() val graph = createGraph() - runBlocking { graph.devSignIn() } + runBlocking { + graph.startup() + graph.devSignIn() + } application { Window( onCloseRequest = ::exitApplication, diff --git a/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt index ad6a2caccc..fa8d2dac4b 100644 --- a/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt @@ -15,9 +15,11 @@ import org.jetbrains.skia.EncodedImageFormat */ fun main(args: Array) { val out = File(args.firstOrNull() ?: "wallet.png") - installImageLoader() val graph = createGraph() - runBlocking { graph.devSignIn() } + runBlocking { + graph.startup() + graph.devSignIn() + } ImageComposeScene(width = 840, height = 1720, density = Density(2f)) { DesktopWalletContent(graph) }.use { scene -> diff --git a/apps/desktop/src/test/kotlin/com/flipcash/desktop/DesktopWiringTest.kt b/apps/desktop/src/test/kotlin/com/flipcash/desktop/DesktopWiringTest.kt new file mode 100644 index 0000000000..43d8ca7e3a --- /dev/null +++ b/apps/desktop/src/test/kotlin/com/flipcash/desktop/DesktopWiringTest.kt @@ -0,0 +1,115 @@ +package com.flipcash.desktop + +import coil3.PlatformContext +import coil3.SingletonImageLoader +import com.flipcash.app.startup.contract.StartupContract +import com.flipcash.app.startup.contract.StartupContract.Desktop +import com.flipcash.app.startup.contract.StartupContract.Step +import com.flipcash.libs.currency.math.Estimator +import com.getcode.opencode.events.Events +import com.hoc081098.channeleventbus.ChannelEventBusException +import com.hoc081098.channeleventbus.ValidationBeforeClosing +import dev.zacsweers.metro.createGraph +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import java.math.BigDecimal +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertSame +import kotlin.test.assertTrue +import kotlin.test.fail + +/** + * Builds the real Metro graph and checks it against [StartupContract]: the session listeners + * desktop must bind, the startup steps it must repeat, and that singletons stay single. Each check + * here stands for a gap that once left the desktop wallet on its loading spinner with no error. + */ +class DesktopWiringTest { + private val graph = createGraph() + + @Test + fun `binds every shared session listener and nothing unclassified`() { + val bound = graph.sessionListeners.map { it::class.qualifiedName }.toSet() + val missing = StartupContract.sharedSessionListeners - bound + assertTrue(missing.isEmpty(), "desktop graph does not bind these SessionListeners: $missing") + val unclassified = bound - StartupContract.sharedSessionListeners - + StartupContract.androidOnlySessionListeners.keys + assertTrue(unclassified.isEmpty(), "SessionListeners missing from StartupContract: $unclassified") + } + + @Test + fun `startup takes effect for every mirrored step`() = runBlocking { + graph.startup() + val steps = StartupContract.androidStartup.values.filterIsInstance().map { it.step } + assertEquals(Step.entries.toSet(), steps.toSet(), "a Step no Android entry point maps to") + for (step in Step.entries) { + // Exhaustive, so a new Step does not compile until it has a check here. + when (step) { + Step.EventRepository -> assertEventRepositoryListening() + Step.CurveTables -> assertTrue( + Estimator.reserveAtSupply(BigDecimal.ONE).isSuccess, + "curve tables not loaded: ${Estimator.reserveAtSupply(BigDecimal.ONE).exceptionOrNull()}", + ) + Step.NetworkImageLoader -> { + val loader = SingletonImageLoader.get(PlatformContext.INSTANCE) + // coil-network-okhttp registers its fetcher through ServiceLoader, so this + // fails when the artifact is off the classpath, which is what blanked the logos. + val factories = loader.components.fetcherFactories.map { it.first::class.qualifiedName } + assertTrue( + factories.any { it?.startsWith("coil3.network.") == true }, + "image loader has no network fetcher: $factories", + ) + // The disk cache comes only from installImageLoader, so this fails when Coil + // built its default loader instead. + assertTrue( + loader.diskCache?.directory?.name == "image_cache", + "Coil is using its default loader, not installImageLoader's", + ) + } + } + } + } + + @Test + fun `scoped bindings resolve to one instance`() { + // Metro once dropped provider-level @Singleton read through Dagger factories, which gave + // every consumer its own event bus; login events then reached nobody. Only bindings scoped + // on Android belong here: AccountController, for one, is unscoped there too. + assertSame(graph.eventBus, graph.eventBus, "ChannelEventBus") + assertSame(graph.userManager, graph.userManager, "UserManager") + assertSame(graph.exchange, graph.exchange, "Exchange") + assertSame(graph.eventRepository, graph.eventRepository, "EventRepository") + assertSame(graph.tokenCoordinator, graph.tokenCoordinator, "TokenCoordinator") + val inSet = graph.sessionListeners.single { it::class == graph.tokenCoordinator::class } + assertSame(graph.tokenCoordinator, inSet, "TokenCoordinator in the listener set is a second instance") + } + + /** + * The repository subscribes to `Events.OnLoggedIn` on the shared bus. Closing that key with + * REQUIRE_FLOW_IS_NOT_COLLECTING fails only while someone collects it; the subscription is + * launched on IO, so give it a moment. + */ + private suspend fun assertEventRepositoryListening() { + val collecting = withTimeoutOrNull(5_000) { + while (true) { + try { + graph.eventBus.closeKey( + Events.OnLoggedIn, + setOf( + ValidationBeforeClosing.REQUIRE_BUS_IS_EXISTING, + ValidationBeforeClosing.REQUIRE_FLOW_IS_NOT_COLLECTING, + ), + ) + fail("Events.OnLoggedIn has a bus but no collector") + } catch (_: ChannelEventBusException.CloseException.BusIsCollecting) { + return@withTimeoutOrNull true + } catch (_: ChannelEventBusException.CloseException.BusDoesNotExist) { + delay(50) + } + } + @Suppress("UNREACHABLE_CODE") false + } + assertEquals(true, collecting, "nothing collects Events.OnLoggedIn: the event repository was never created") + } +} diff --git a/apps/flipcash/app/build.gradle.kts b/apps/flipcash/app/build.gradle.kts index 8c621ee69d..ad8a022efd 100644 --- a/apps/flipcash/app/build.gradle.kts +++ b/apps/flipcash/app/build.gradle.kts @@ -374,4 +374,5 @@ dependencies { testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.mockk) testImplementation(libs.robolectric) + testImplementation(project(":apps:flipcash:startup-contract")) } diff --git a/apps/flipcash/app/src/test/kotlin/com/flipcash/app/AndroidWiringTest.kt b/apps/flipcash/app/src/test/kotlin/com/flipcash/app/AndroidWiringTest.kt new file mode 100644 index 0000000000..5d6f363e51 --- /dev/null +++ b/apps/flipcash/app/src/test/kotlin/com/flipcash/app/AndroidWiringTest.kt @@ -0,0 +1,105 @@ +package com.flipcash.app + +import coil3.SingletonImageLoader +import com.flipcash.app.inject.ApiModule +import com.flipcash.app.startup.contract.StartupContract +import com.getcode.opencode.providers.SessionListener +import com.mixpanel.android.mpmetrics.MixpanelAPI +import dagger.hilt.android.testing.BindValue +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.HiltTestApplication +import dagger.hilt.android.testing.UninstallModules +import io.mockk.mockk +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.w3c.dom.Element +import java.io.File +import java.lang.reflect.ParameterizedType +import javax.inject.Inject +import javax.xml.parsers.DocumentBuilderFactory +import kotlin.test.assertEquals + +/** + * Checks the Android app's wiring against [StartupContract], so a session listener or startup entry + * point added here has to be classified there before the build passes, and the desktop graph's own + * test then holds desktop to it. + * + * `HiltTestApplication` replaces [FlipcashApp], so this does not run Android's startup; it checks + * the listener set the real Hilt graph builds and that every startup entry point is accounted for. + * + * [ApiModule] is replaced because `MixpanelAPI.getInstance` returns null under Robolectric; it has no + * part in session listeners or startup. + */ +@HiltAndroidTest +@UninstallModules(ApiModule::class) +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, application = HiltTestApplication::class, sdk = [35]) +class AndroidWiringTest { + @get:Rule + val hilt = HiltAndroidRule(this) + + @BindValue + val mixpanel: MixpanelAPI = mockk(relaxed = true) + + @Inject + lateinit var sessionListeners: Set<@JvmSuppressWildcards SessionListener> + + @Before + fun setUp() = hilt.inject() + + @Test + fun `session listeners are exactly the classified set`() { + val bound = sessionListeners.map { it::class.java.name }.toSet() + val classified = StartupContract.sharedSessionListeners + StartupContract.androidOnlySessionListeners.keys + assertEquals(classified, bound, "classify each SessionListener in StartupContract") + } + + @Test + fun `every startup entry point is classified`() { + assertEquals( + StartupContract.androidStartup.keys.sorted(), + (manifestInitializers() + appInjections() + appImageLoader()).sorted(), + "classify each startup entry point in StartupContract.androidStartup", + ) + } + + /** androidx-startup initializers this module's manifest adds (not the ones it removes). */ + private fun manifestInitializers(): List { + val doc = DocumentBuilderFactory.newInstance().apply { isNamespaceAware = true } + .newDocumentBuilder().parse(File("src/main/AndroidManifest.xml")) + val metaData = doc.getElementsByTagName("meta-data") + return (0 until metaData.length).map { metaData.item(it) as Element } + .filter { it.getAttributeNS(ANDROID, "value") == "androidx.startup" } + .filter { it.getAttributeNS(TOOLS, "node") != "remove" } + .map { it.getAttributeNS(ANDROID, "name") } + } + + /** What [FlipcashApp] injects, unwrapping `Lazy` to `T`. */ + private fun appInjections(): List = + FlipcashApp::class.java.declaredFields + .filter { it.isAnnotationPresent(Inject::class.java) } + .map { field -> + val type = field.genericType + if (type is ParameterizedType && type.rawType == dagger.Lazy::class.java) { + (type.actualTypeArguments.single() as Class<*>).name + } else { + field.type.name + } + } + + private fun appImageLoader(): List = + listOfNotNull( + SingletonImageLoader.Factory::class.java.name + .takeIf { SingletonImageLoader.Factory::class.java.isAssignableFrom(FlipcashApp::class.java) }, + ) + + private companion object { + const val ANDROID = "http://schemas.android.com/apk/res/android" + const val TOOLS = "http://schemas.android.com/tools" + } +} diff --git a/apps/flipcash/startup-contract/build.gradle.kts b/apps/flipcash/startup-contract/build.gradle.kts new file mode 100644 index 0000000000..95fcdf30b4 --- /dev/null +++ b/apps/flipcash/startup-contract/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + alias(libs.plugins.flipcash.kmp.app.library) +} + +// Test-only: the startup wiring both the Android and desktop graphs are checked against. +kotlin { + android { + namespace = "${Gradle.flipcashNamespace}.startup.contract" + } +} diff --git a/apps/flipcash/startup-contract/src/commonMain/kotlin/com/flipcash/app/startup/contract/StartupContract.kt b/apps/flipcash/startup-contract/src/commonMain/kotlin/com/flipcash/app/startup/contract/StartupContract.kt new file mode 100644 index 0000000000..978d9a48c5 --- /dev/null +++ b/apps/flipcash/startup-contract/src/commonMain/kotlin/com/flipcash/app/startup/contract/StartupContract.kt @@ -0,0 +1,87 @@ +package com.flipcash.app.startup.contract + +/** + * The wiring the wallet's balance path needs before its first screen, written down once so the + * Android and desktop wiring tests check the same list. + * + * Desktop rebuilds by hand what Android gets from Hilt modules, androidx-startup initializers and + * `FlipcashApp.onCreate`. Each piece it missed in the spike (the token coordinator's listener + * binding, the eager event repository, the curve tables, the network image loader) left the wallet + * on a spinner or blank logos with no error. Here, Android's side has to classify every startup + * entry point it has, and desktop has to prove each mirrored step took effect. + */ +object StartupContract { + + /** A piece of startup work desktop has to repeat. The desktop test checks each one's effect. */ + enum class Step { + /** The event repository exists, so `Events.OnLoggedIn` reaches the session listeners. */ + EventRepository, + + /** The discrete curve tables are loaded; every balance valuation throws without them. */ + CurveTables, + + /** The image loader can fetch https URLs, which is where token logos live. */ + NetworkImageLoader, + } + + sealed interface Desktop { + data class Mirrored(val step: Step) : Desktop + data class NotOnDesktop(val reason: String) : Desktop + } + + /** `SessionListener` implementations desktop must bind as well. Fully qualified class names. */ + val sharedSessionListeners: Set = setOf( + "com.flipcash.app.tokens.TokenCoordinator", + ) + + /** `SessionListener` implementations only Android binds, with why desktop goes without. */ + val androidOnlySessionListeners: Map = mapOf( + "com.flipcash.shared.chat.internal.RealChatCoordinator" to "chat is not on desktop", + "com.flipcash.app.contacts.ContactCoordinator" to "contacts are not on desktop", + ) + + /** + * Every startup entry point the Android app declares: the androidx-startup initializers in its + * manifest, the types `FlipcashApp` injects, and `SingletonImageLoader.Factory`, which it + * implements. Keyed by fully qualified name. + */ + val androidStartup: Map = mapOf( + // androidx-startup initializers + "com.flipcash.app.internal.startup.TraceInitializer" to + Desktop.NotOnDesktop("trace export and Bugsnag breadcrumbs are Android tooling"), + "com.flipcash.app.internal.startup.ClockDriftInitializer" to + Desktop.NotOnDesktop("diagnostic only: records SNTP drift for the trace header"), + "com.flipcash.app.internal.startup.FirebaseInitializer" to + Desktop.NotOnDesktop("Firebase is Android-only"), + "com.flipcash.app.internal.startup.LibSodiumInitializer" to + Desktop.NotOnDesktop("the balance path makes no libsodium call; chat crypto does, and chat is not on desktop"), + "com.flipcash.app.internal.startup.MnemonicCacheInitializer" to + Desktop.NotOnDesktop("preloads the BIP39 word list; it loads on first use otherwise"), + "com.flipcash.app.internal.startup.DiscreteBondingCurveInitializer" to + Desktop.Mirrored(Step.CurveTables), + "com.flipcash.app.internal.startup.CameraXInitializer" to + Desktop.NotOnDesktop("no camera on desktop"), + + // FlipcashApp's injected dependencies + "com.flipcash.app.auth.AuthManager" to + Desktop.NotOnDesktop("restores the stored session; desktop signs in through DevSession until Keychain storage lands"), + "com.getcode.opencode.repositories.EventRepository" to + Desktop.Mirrored(Step.EventRepository), + "com.flipcash.app.currency.PreferredCurrencyController" to + Desktop.NotOnDesktop("the exchange falls back to the locale's currency"), + "com.flipcash.shared.chat.media.ChatPhotoFetcher\$Factory" to + Desktop.NotOnDesktop("chat is not on desktop"), + "com.flipcash.app.messenger.LinkPreviewStartup" to + Desktop.NotOnDesktop("link previews belong to chat"), + "androidx.hilt.work.HiltWorkerFactory" to + Desktop.NotOnDesktop("WorkManager is Android-only"), + "com.flipcash.app.core.android.ActivityProvider" to + Desktop.NotOnDesktop("tracks the foreground Activity"), + "com.flipcash.app.bills.share.TipCodePreviewCache" to + Desktop.NotOnDesktop("tip-code share previews are not on desktop"), + + // FlipcashApp implements this to build Coil's singleton loader + "coil3.SingletonImageLoader\$Factory" to + Desktop.Mirrored(Step.NetworkImageLoader), + ) +} diff --git a/services/opencode/src/jvmMain/kotlin/com/getcode/opencode/internal/platform/ProcessLifecycle.jvm.kt b/services/opencode/src/jvmMain/kotlin/com/getcode/opencode/internal/platform/ProcessLifecycle.jvm.kt index d00eadf201..381064e457 100644 --- a/services/opencode/src/jvmMain/kotlin/com/getcode/opencode/internal/platform/ProcessLifecycle.jvm.kt +++ b/services/opencode/src/jvmMain/kotlin/com/getcode/opencode/internal/platform/ProcessLifecycle.jvm.kt @@ -4,11 +4,18 @@ import java.util.concurrent.Executors private val dispatcher = Executors.newSingleThreadExecutor { Thread(it, "process-lifecycle").apply { isDaemon = true } } +/** + * Set to `false` to keep [onStart] from running, so tests can build the graph without the + * exchange opening its rate stream to the backend. + */ +private const val AUTO_START_PROPERTY = "opencode.processLifecycle.autoStart" + /** A desktop process is in the foreground for as long as it runs, so only [onStart] is ever called. */ internal actual fun observeProcessLifecycle( immediateOnMain: Boolean, onStart: () -> Unit, onStop: () -> Unit, ) { + if (System.getProperty(AUTO_START_PROPERTY) == "false") return dispatcher.execute(onStart) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 593daac175..98137caca5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -119,6 +119,7 @@ include( ":apps:flipcash:core", ":apps:flipcash:core-ui", ":apps:flipcash:card-expand", + ":apps:flipcash:startup-contract", ":libs:test-utils", // shared flipcash coordinators/controllers/viewmodels/services ":apps:flipcash:shared:amount-entry",