Repository navigation
Conversation
…ose resources :apps:desktop is a kotlin.jvm + compose.desktop app with an empty Metro graph. ui:resources now also generates a Compose Multiplatform Res class from its Android res/ through a Sync task, so common code can read the same strings and drawables without a second copy in the repo.
Fonts load through an expect seam: Android keeps R.font, desktop reads the same files from the classpath. Dynamic color and the View helpers stay in androidMain.
Only the files the balance screen reaches move to commonMain; the rest stay in androidMain unchanged. Drawable lookups by Android resource id go through painterForResId, with a desktop ResourceHelper over Compose resources. Blur support and hex colour parsing get small platform seams.
CodeNavigator, its options, flow scope and the nav-result types move to commonMain; navigation3 1.1.4 publishes a desktop variant, so the back stack type is the same artifact on both platforms. Scenes, flow hosts and AppNavHost stay in androidMain. RepeatOnLifecycle reads LocalLifecycleOwner from lifecycle-runtime-compose, the class the old androidx.compose.ui.platform one already aliases on Android.
The FlipcashAnalytics interface, the token symbol resolver and the CompositionLocal move to common code. Conversions (toUri and app:core's DeeplinkType) and the Mixpanel delegate stay Android. RecordingAnalytics moves to a :test-fixtures module, since the KMP Android target has no testFixtures; its 10 consumers point at that module. Also moves ui:navigation's BaseViewModel to common code; it uses only lifecycle and coroutines.
Flag labels sit behind a FlagStrings expect object (Android keeps the R.string ids; desktop has no flag editor). UserFlagsFiles replaces the Context parameter so the DataStore file comes from the platform.
AppRoute, its step and result types, DeviceContact and TipCardOwner move to app:core's commonMain with the shared Parcelable aliases. The three files that use Uri or R (CurrencyCreatorDraft, TokenDisplayName, DeeplinkType) stay in androidMain. FlowRoute moves to ui:navigation's commonMain. ui:resources adds stringRes/pluralStringRes for composables that hold an Int resource ID, and lets other modules register their own Compose resources with ComposeResourceIds to get an Int ID on desktop.
… composables Lifts the nine composables the balance screen and activity feed render (TileButton, TokenCard, TokenCardStack, TokenImageWithName, ViewerAmount, ActivityAmount, CurrencyAppreciationLabel, FlagWithFiat, NewUserTutorial) to commonMain. Everything else stays in androidMain unchanged. Seams: screenHeightPx (Configuration on Android, window container on desktop) and ImageRequestPlaceholders for Coil placeholders. Flags load through painterForResId. app:core exposes its res/ as Compose resources and gains CoreStrings/CoreDrawables id objects for the lifted files. opencode-compose moves to KMP with LocalExchange in commonMain.
…vatar BlurHash decodes to pixels in common code; each platform wraps them (Android still builds the same ARGB_8888 Bitmap, and keeps BlurHash.decode as an extension). ContactAvatar uses Coil's LocalPlatformContext and passes the URL string to Coil, which maps it as it did the Uri. app:core's media resolver and TimeProvider move to common; the Coil-backed ImageCachePresence stays Android.
Lift the activity feed coordinator and updater, both mappers and the recent-activity composables to commonMain. String ids go through a TxStrings expect object (R.string on Android, the Compose resource registry on desktop); the 24-hour clock check is an expect seam. NetworkUpdater moves to app:core's commonMain.
PurchaseMethod, PurchaseMethodController and PurchaseMethodState move to commonMain so the balance view model can take the controller on desktop. The controller implementation and its UI stay in androidMain. The module moves to flipcash.kmp.app.library, since its app-module dependencies have no Apple targets.
…let coordinators TokenCoordinator, WalletRevealCoordinator, TokenSelectionResolver and SelectTokenViewModel move to commonMain; the rest of the module stays in androidMain. Two app:core seams replace the Android types TokenCoordinator used: - PreferencesFiles hands out the DataStore file; Android binds it to Context.preferencesDataStoreFile through Hilt. - AppProcessLifecycle is ProcessLifecycleOwner on Android and a lifecycle held in RESUMED on desktop, since lifecycle-process has no desktop artifact. SelectTokenViewModel becomes an open class without Hilt annotations. HiltSelectTokenViewModel in androidMain carries @hiltviewmodel, and the ten hiltViewModel<SelectTokenViewModel>() call sites across eight features now request it.
WalletViewModel becomes an open class in commonMain with an @Inject constructor; the @hiltviewmodel subclass HiltWalletViewModel stays in androidMain, and the Android entry point asks Hilt for it. The wallet content and header composables move to commonMain and read their strings and drawables through CoreStrings/CoreDrawables. The view model only called ChatCoordinator.hasEverTipped(), so it now takes a one-method TipHistorySource from app:core. ChatCoordinator extends it and ChatModule binds it, so Android injects the same object and balance no longer depends on shared:chat (10k lines). ReportDrawnWhen becomes an expect seam (no-op on jvm). card-expand moves to android+jvm with everything in commonMain.
Desktop builds WalletViewModel from a Metro graph that reuses the Dagger modules Android's Hilt graph installs. OpenCodeModule and FlipcashModule split into shared providers (commonMain), the Android channel/protocol config (androidMain) and an okhttp channel module in apps/desktop. Internal @provides functions carry @JvmName so Dagger's factory names stay unmangled for Metro's runtime interop. DesktopModule binds the ten seams Android provides from Hilt modules; funding, analytics and connectivity are stubs. shared:theme moves to common so the window paints the Flipcash colour scheme.
DevSession reads FLIPCASH_DEV_MNEMONIC, which Gradle sets for the desktop JavaExec tasks only when local.properties has flipcash.test.mnemonic. It goes in the environment rather than a -D flag so the phrase stays out of the process command line, and it is never logged. Four gaps kept the wallet on its loading state after sign-in: - Nothing constructed EventRepository, which turns Events.OnLoggedIn into SessionListener.onUserLoggedIn. Android builds it eagerly in FlipcashApp; the graph now exposes it and devSignIn touches it first. - TokenCoordinator joins the listener set in TokenModule, which is Android-only. DesktopModule now adds it. - Through Dagger's generated factories, Metro 1.4.5 drops provider-level @singleton, so each injection site got its own ChannelEventBus and events never reached the repository. services:opencode now applies Metro with Dagger interop and generates its own factories instead of running dagger-compiler on jvm. - The bonding curve tables were never loaded, so every balance threw "DiscreteBondingCurve not initialized" inside a retry that swallowed it. devSignIn calls Curves.initialize() first, as Android's Application does. OpenCodeExchange's state fields move above init: init hands streamRates to another thread, which could read them before they were assigned.
Token logos are https URLs. Coil 3 ships no network fetcher, and the desktop app had no coil-network artifact on its classpath, so every logo failed with "Unable to create a fetcher" and the card stack showed blank avatars. coil-network-okhttp registers its fetcher through ServiceLoader, so adding the dependency is the fix. installImageLoader mirrors FlipcashApp.newImageLoader (crossfade, memory cache, 50 MB disk cache), minus the chat photo keyer and fetcher.
…tract Each wiring gap in A2 (the token coordinator's listener binding, the eager event repository, the curve tables, the network image loader) left the desktop wallet on a spinner or blank logos with no error. :apps:flipcash:startup-contract lists the SessionListeners desktop must bind, the ones only Android binds, and every Android startup entry point, each marked as mirrored on desktop (with a Step) or not, with a reason. - AndroidWiringTest builds the real Hilt graph under Robolectric and fails when a SessionListener or startup entry point (manifest initializer, FlipcashApp injection, SingletonImageLoader.Factory) is missing from the contract. ApiModule is replaced because MixpanelAPI.getInstance returns null under Robolectric. - DesktopWiringTest builds the real Metro graph, runs DesktopGraph.startup(), and checks the effect of each mirrored step, the listener set, and that scoped bindings resolve to one instance. The desktop startup steps move out of devSignIn into startup() so the test can run them without a sign-in phrase. Tests set opencode.processLifecycle.autoStart=false so building the exchange does not open its rate stream to the backend.
Collaborator
Author
|
Landed in #1894 as one squash against code/cash. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1843. Moves the UI modules on the balance path to android + jvm KMP and adds
:apps:desktop, a Compose Desktop window that renders the wallet screen with a live balance from the same view models Android uses.What moves
ui:theme,ui:core,ui:components,ui:navigation,ui:resources,shared:analytics,shared:userflags,core,core-ui,common-ui,shared:transaction-history,shared:funding,shared:tokens,features:balance.src/mainmoves tosrc/androidMain, and only the files the balance screen needs are lifted into commonMain. Everything else stays Android-only, so the Android app is unchanged apart from imports.open class WalletViewModel @Inject constructor(...); androidMain adds a@HiltViewModelsubclass, and Android call sites use it. Desktop gets the common class from the Metro graph.shared:transaction-historyno longer depends onshared:chat. It reads tips through aTipHistorySourceinterface.Resources and icons
AndroidResAsComposeResources(build-logic) syncs each module'sres/valuesandres/drawableinto Compose resources at build time. Common code reads them through per-module id objects (CoreStrings,CoreDrawables,TxStrings, ...) whose Android actuals areRids. Vector XML is not hand-ported or duplicated; 8 drawables are referenced from common code so far.Desktop DI
DesktopGraphis a Metro graph over the existing Dagger modules (javax/Dagger interop). Android keeps Hilt. Hilt runs throughkspAndroidwithout the Hilt Gradle plugin, which refuses the KMP android target; only the app and notifications modules apply the plugin.Wiring that Android does in Android-only places had to be repeated in
DesktopModule:TokenCoordinatorjoins theSessionListenerset (Android:TokenModule).EventRepositoryis constructed before login (Android:FlipcashApp).Curves.initialize()loads the bonding curve tables (Android: the Application).services:opencodenow applies Metro itself instead of running dagger-compiler on jvm. Through Dagger's generated factories, Metro 1.4.5 dropped provider-level@Singleton, so every injection site got its ownChannelEventBus.Dev sign-in
Until link-from-phone lands (A3), desktop signs in with
flipcash.test.mnemonicfromlocal.properties. Gradle passes it to the desktopJavaExectasks asFLIPCASH_DEV_MNEMONIC, only when set. It is not on the command line and never logged. Without it the window opens signed out.Run:
./gradlew :apps:desktop:run, or./gradlew :apps:desktop:snapshot -Pout=wallet.pngfor an off-screen render.Known gaps