diff --git a/apps/desktop/build.gradle.kts b/apps/desktop/build.gradle.kts new file mode 100644 index 0000000000..9fd564a8f6 --- /dev/null +++ b/apps/desktop/build.gradle.kts @@ -0,0 +1,88 @@ +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) +} + +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) + 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 +// 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" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "Flipcash" + packageVersion = "1.0.0" + } + } +} + +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)) +} + +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 new file mode 100644 index 0000000000..42fa4573bd --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopGraph.kt @@ -0,0 +1,51 @@ +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 javax.inject.Singleton + +@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 walletViewModel: WalletViewModel + val selectTokenViewModel: SelectTokenViewModel + val exchange: Exchange + 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 + + // 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/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/DesktopModule.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt new file mode 100644 index 0000000000..f984eb8a38 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DesktopModule.kt @@ -0,0 +1,120 @@ +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.SessionListener +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 dagger.multibindings.IntoSet +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 { + /** 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() } + } + + @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 + + // 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 { + 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/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/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/DevSession.kt b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt new file mode 100644 index 0000000000..5663043352 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/DevSession.kt @@ -0,0 +1,45 @@ +package com.flipcash.desktop + +import com.flipcash.app.persistence.FlipcashDatabase +import com.flipcash.app.persistence.init +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. Run [startup] first. */ +internal suspend fun DesktopGraph.devSignIn() { + 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 new file mode 100644 index 0000000000..df61615149 --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/Main.kt @@ -0,0 +1,32 @@ +package com.flipcash.desktop + +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 kotlinx.coroutines.runBlocking +import java.lang.management.ManagementFactory + +fun main() { + val graph = createGraph() + runBlocking { + graph.startup() + graph.devSignIn() + } + application { + Window( + onCloseRequest = ::exitApplication, + title = "Flipcash", + state = rememberWindowState(width = 420.dp, height = 860.dp), + ) { + DesktopWalletContent(graph) + LaunchedEffect(Unit) { + val sinceJvmStart = System.currentTimeMillis() - + ManagementFactory.getRuntimeMXBean().startTime + println("startup: first composition ${sinceJvmStart}ms after JVM start") + } + } + } +} 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..fa8d2dac4b --- /dev/null +++ b/apps/desktop/src/main/kotlin/com/flipcash/desktop/SnapshotMain.kt @@ -0,0 +1,37 @@ +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 kotlinx.coroutines.runBlocking +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() + runBlocking { + graph.startup() + graph.devSignIn() + } + 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/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/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-ui/build.gradle.kts b/apps/flipcash/core-ui/build.gradle.kts index 8e3f3d0dff..5c27c4add1 100644 --- a/apps/flipcash/core-ui/build.gradle.kts +++ b/apps/flipcash/core-ui/build.gradle.kts @@ -1,18 +1,60 @@ 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) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } 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 0967c1843a..2fab73b963 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) } @@ -14,9 +17,19 @@ kotlin { sourceSets { 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) implementation(libs.kotlinx.serialization.core) 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")) api(project(":services:opencode")) @@ -42,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) @@ -66,7 +80,18 @@ 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) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } 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/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/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..fd2dd31111 --- /dev/null +++ b/apps/flipcash/core/src/androidMain/kotlin/com/flipcash/app/core/resources/CoreResources.android.kt @@ -0,0 +1,33 @@ +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 + 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") +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/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/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/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/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/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/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/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/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/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..28e1615421 --- /dev/null +++ b/apps/flipcash/core/src/commonMain/kotlin/com/flipcash/app/core/resources/CoreResources.kt @@ -0,0 +1,36 @@ +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 + 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") +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/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/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/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/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/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/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/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..92b968fcf4 --- /dev/null +++ b/apps/flipcash/core/src/jvmMain/kotlin/com/flipcash/app/core/resources/CoreResources.jvm.kt @@ -0,0 +1,55 @@ +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.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") +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) + 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") +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/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/build.gradle.kts b/apps/flipcash/features/balance/build.gradle.kts index 35b9361647..5ee4523ab2 100644 --- a/apps/flipcash/features/balance/build.gradle.kts +++ b/apps/flipcash/features/balance/build.gradle.kts @@ -1,29 +1,67 @@ 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(testFixtures(project(":apps:flipcash:shared:analytics"))) - 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) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } 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 88% 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 5bfd043f03..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,12 +7,14 @@ 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 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 @@ -26,8 +28,8 @@ fun WalletScreen() { modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { - val viewModel = hiltViewModel() - val tokenViewModel = hiltViewModel() + val viewModel = hiltViewModel() + val tokenViewModel = hiltViewModel() WalletScreen(viewModel, tokenViewModel) LaunchedEffect(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..0980c6a663 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 @@ -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, ) { @@ -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/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/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/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/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/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/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/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/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/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/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/analytics/build.gradle.kts b/apps/flipcash/shared/analytics/build.gradle.kts index c17b1a59e6..a93d95a77b 100644 --- a/apps/flipcash/shared/analytics/build.gradle.kts +++ b/apps/flipcash/shared/analytics/build.gradle.kts @@ -1,32 +1,42 @@ 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) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } 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/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/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/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( 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() diff --git a/apps/flipcash/shared/funding/build.gradle.kts b/apps/flipcash/shared/funding/build.gradle.kts index 03fc03ffca..23ec4527ef 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) @@ -41,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/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 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/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/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 e7825e9269..34e4d3caf8 100644 --- a/apps/flipcash/shared/tokens/build.gradle.kts +++ b/apps/flipcash/shared/tokens/build.gradle.kts @@ -1,38 +1,76 @@ 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(testFixtures(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: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) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } 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, diff --git a/apps/flipcash/shared/transaction-history/build.gradle.kts b/apps/flipcash/shared/transaction-history/build.gradle.kts index ee44a7fbf1..5e5ccc3462 100644 --- a/apps/flipcash/shared/transaction-history/build.gradle.kts +++ b/apps/flipcash/shared/transaction-history/build.gradle.kts @@ -1,30 +1,86 @@ -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) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } 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) +} diff --git a/apps/flipcash/shared/userflags/build.gradle.kts b/apps/flipcash/shared/userflags/build.gradle.kts index 38e90ecf0e..bcb6712ac2 100644 --- a/apps/flipcash/shared/userflags/build.gradle.kts +++ b/apps/flipcash/shared/userflags/build.gradle.kts @@ -1,27 +1,47 @@ 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) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } 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/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/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 89% 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 index 833a8bf162..6f94b50caa 100644 --- 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 @@ -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/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/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/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 96% 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 index 04a5c91e03..fb9bb49c9c 100644 --- 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 @@ -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/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/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/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/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..fec7c51eff 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" @@ -144,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" } @@ -160,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" } @@ -177,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" } @@ -190,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" } @@ -340,7 +350,14 @@ 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" } +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] compose = ["compose-ui", "compose-foundation", "compose-material", "compose-material-icons-extended", "compose-animation", "compose-activities"] @@ -383,4 +400,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/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-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 diff --git a/services/opencode/build.gradle.kts b/services/opencode/build.gradle.kts index bccc54869c..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,6 +91,7 @@ kotlin { dependencies { add("kspAndroid", libs.hilt.android.compiler) add("kspAndroid", libs.hilt.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/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() 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 99cf20e5ba..98137caca5 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", @@ -118,11 +119,13 @@ 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", ":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/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/navigation/build.gradle.kts b/ui/navigation/build.gradle.kts index 6e8e974f1f..c658b24e56 100644 --- a/ui/navigation/build.gradle.kts +++ b/ui/navigation/build.gradle.kts @@ -1,40 +1,57 @@ 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) + add("kspJvm", libs.dagger.compiler) + add("jvmMainImplementation", libs.dagger) } 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/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/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/FlowRoute.kt b/ui/navigation/src/commonMain/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/commonMain/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/commonMain/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/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 diff --git a/ui/navigation/src/main/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/main/kotlin/com/getcode/view/BaseViewModel.kt rename to ui/navigation/src/commonMain/kotlin/com/getcode/view/BaseViewModel.kt 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(), + ) +} 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/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/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/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/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..9a15779c2c --- /dev/null +++ b/ui/resources/src/jvmMain/kotlin/com/getcode/util/resources/ComposeResourceIds.kt @@ -0,0 +1,71 @@ +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. + * + * `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 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 } + + 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)) 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) 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), + ) +}