feat: surface a call to action while no wallet descriptor is loaded - #146
Merged
Conversation
The descriptor is a hard prerequisite — the node only watches addresses it knows about — but its only entry point was a collapsed Descriptors card on the 4th tab. First-run users never found it, so the app looked broken rather than unconfigured. Descriptor state also lived solely in SettingsViewModel, which the pager does not construct until the user swipes to page 2, so no other screen could know whether one was loaded. Lift it into a WalletDescriptorRepository singleton that polls listdescriptors. Its WalletDescriptorStatus.isKnown flag separates "the daemon has not answered yet" from "the wallet is empty", so nothing prompts during the RPC boot window. Non-empty is terminal (there is no unload RPC), so the poll loop exits for good once a descriptor exists. Three surfaces hang off that state: - a manually dismissable snackbar on the Node tab, rendered beside SnackbarHost rather than through it — showSnackbar(Indefinite) would hold the single host slot and queue the snapshot and clipboard messages behind it - a Settings nav badge, ORed with the existing update badge so markUpdateSeen keeps working - an empty state on the Transactions tab replacing the 12sp grey hint All three deep-link to Settings with the Descriptors section expanded and scrolled into view, via a new ExpandDescriptors action so an already-open card cannot be collapsed. Also fixes a latent bug: the old getDescriptors() only handled onSuccess, so an RPC failure silently read as an empty wallet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Problem
Loading a wallet descriptor is a hard prerequisite — the node only watches addresses it knows about — but the only entry point was a collapsed
Descriptorscard, item 4 on the 4th tab. Nothing else in the app mentioned it. A first-run user lands on the Node tab, watches it sync, and never learns the step exists, so the app looks broken rather than unconfigured.Descriptor state also lived solely in
SettingsViewModel, which the pager doesn't construct until the user swipes to page 2 (beyondViewportPageCount = 1) — so no other screen could know whether one was loaded.Approach
New
WalletDescriptorRepositorysingleton pollinglistdescriptorsas the app-wide source of truth.WalletDescriptorStatus.isKnownis the anti-flash guard: it separates "the daemon hasn't answered yet" from "the wallet is genuinely empty", so nothing prompts during the RPC boot window. Non-empty is terminal (Floresta has no unload RPC), so the poll loop exits for good on the happy path and never polls again.Three surfaces hang off it:
SnackbarbesideSnackbarHostin the Scaffold slot, not viashowSnackbar(Indefinite), which would hold the single host slot and queue the Utreexo snapshot and clipboard-hint messages behind it. Dismissal isrememberSaveable, so it survives rotation and returns on the next launch while no descriptor is loaded.markUpdateSeen()semantics.AppNavigationBar/Rail/DestinationIconare untouched, so the tablet rail is covered for free.TransactionLookupCard, which phone and tablet share, so one edit covers both.All three deep-link to Settings with the Descriptors section expanded and scrolled into view, via a new
ExpandDescriptorsaction (not the toggle, which would collapse an already-open card).Also fixes a latent bug: the old
getDescriptors()only handledonSuccess, so an RPC failure silently read as an empty wallet.Verification
Verified end-to-end on a wiped x86_64 emulator against mainnet with real peers:
listdescriptorshas answered twiceinput_descriptoron screen, no manual scrollingrefresh(), not the poll); restart showed no flash./gradlew test detekt lintDebugpasses — 344 unit tests, including a newWalletDescriptorRepositoryImplTest(anti-flash, retry, poll termination, out-of-band refresh) andMainViewModelTest(badge OR truth table).Notes for review
config/detekt/detekt.yml: raisedLongParameterList.constructorThresholdto 9.SettingsViewModelhit exactly 7 injected collaborators; a holder object would only hide the dependencies.testTagon aSnackbarroot never surfaces inandroid layout(the root emits no semantics), so the tag rides the messageText. This keeps the action and ✕ independently targetable — documented injourneys/README.md.DESCRIPTORS_ITEM_INDEX = 3inScreenSettings.ktis positional;LazyVerticalStaggeredGridhas no scroll-to-key API. Commented accordingly.NodeViewModel.getInLoop()'s existing 10 s poll.🤖 Generated with Claude Code