feat: give the app somewhere to report an outcome, and every dead-end error a way out
Phase 5, first step, of docs/material-design-conformance.md. Two absences, both structural.
**Sixteen copies of the same dead end.** The tree held sixteen instances of
Column(horizontalAlignment = CenterHorizontally) {
Spacer(Modifier.height(48.dp))
Text("Something went wrong")
}
and five of the same shape saying "No events were found". **Not one of the sixteen offered
a retry.** Every failure in this app named no cause and had no way forward but the back
button.
`ErrorState` and `EmptyState` replace all 21. Deliberately plain -- an icon, a line, and
for errors an action when the caller has one to give. `ErrorState`'s `onRetry` is nullable
so that passing null is a *decision* a reader can see, rather than the absence of a
parameter nobody thought about.
`EmptyState`'s message is **required**, with no default, and that is the point of the
change rather than a detail. "No events were found" was shown for five different absences:
nobody you follow, nobody following you, an empty feed, no replies, no search results. A
shared default would have preserved exactly that. They now read "You aren't following
anyone yet.", "Nobody is following you yet.", "Nothing in this feed yet.", "No replies to
this yet." and "Nothing matched that search." -- and `no_events_were_found` is deleted.
**Zero snackbars across 43 Scaffolds.** No `Snackbar`, no `SnackbarHost`, no
`SnackbarHostState` anywhere. Every transient outcome -- an invite failing, a key package
published, a message not sent -- had nowhere to be reported, so the code either said
nothing or navigated away and hoped.
`LocalSnackbarHostState` is a composition local rather than a parameter because of where
the reporting happens: a view model coroutine finishing a call is several composables below
the `Scaffold` that owns the host, and threading the state down would be the same plumbing
repeated 43 times and forgotten on the 44th. One host is provided in `MantraApp`; only one
Scaffold is composed at a time under a NavHost, so the message renders on whichever screen
is on top.
It **throws** rather than defaulting to a detached `SnackbarHostState()`. A default would
make `notify(...)` a silent no-op on any screen that forgot the host, which is precisely
the failure this file exists to end.
**Wired to a real action, not left as infrastructure.** `publishNewKeyPackage` and
`rotateKeyPackage` were fire and forget: you tapped, a coroutine ran, and nothing on screen
changed -- indistinguishable from a tap that missed. Both take an `onDone` and the screen
reports it. Verified on emulator-5554: tapping Publish shows "Key package published" and
the count goes 2 -> 3.
**Externalising the strings made four copy problems visible, which is the argument for
having done it.** With 364 strings in one file rather than scattered through 60
composables, `%1$s Key Packages`, `replying To %1$s` and **three surviving mentions of the
old product name** were sitting in plain sight. All corrected. (They had been fixed once
already and lost: the previous commit reverted the tree to fix an unrelated import bug and
re-ran the extractor over the original text. Worth recording, because it is what a
revert-and-redo costs when a script is the thing being iterated on.)
**And it made the title-case checker stop covering anything.** `m3-title-case.py` scanned
`.kt` files, so when phase 4 moved the strings out it went on reporting zero while the four
above sat in `strings.xml`. It now reads the catalogue too, and that path is verified by
flipping one entry to "Try Again" and watching it fail. Externalising narrows what a source
scan can see; the check has to follow.
**Tests.** 949 pass, 600 jvm over 73 classes and 349 android over 44, unchanged. The state
composables and the snackbar host are composition-time behaviour and this repo has no
Compose UI test infrastructure; what stands in for it is the device run above.
`m3-audit.sh --check` exits 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2117e22d48
commit
44bf2a01f0
54 files changed
+457
-214
No files matched your search
@@ -93,7 +93,7 @@
|
||||
<string name="has_not_taken_part_yet">Has not taken part yet</string>
|
||||
<string name="how_many_admins_have_to_approve_a_change">How many admins have to approve a change?</string>
|
||||
<string name="how_many_members_will_it_take_to_sign">How many members will it take to sign?</string>
|
||||
<string name="if_you_are_new_to_torch_or_just_want_to">If you are new to Torch or just want to create a fresh profile</string>
|
||||
<string name="if_you_are_new_to_torch_or_just_want_to">If you are new to Mantra or just want to create a fresh profile</string>
|
||||
<string name="initial_version_label">Initial version label</string>
|
||||
<string name="input_npub_or_nip05">Input npub... or nip05</string>
|
||||
<string name="introduce_yourself">Introduce yourself</string>
|
||||
@@ -138,7 +138,6 @@
|
||||
<string name="no_chunks">No chunks.</string>
|
||||
<string name="no_dialects_have_been_defined_in_this_group">No dialects have been defined in this group yet. Add one from the group's detail screen first.</string>
|
||||
<string name="no_dialects_have_been_defined_in_this_group_2">No dialects have been defined in this group.</string>
|
||||
<string name="no_events_were_found">No events were found</string>
|
||||
<string name="no_messages_go_to_a_profile_and_send_them_a">No messages. Go to a profile and send them a message.</string>
|
||||
<string name="no_one_selected_yet">No one selected yet</string>
|
||||
<string name="no_one_to_add_yet">No one to add yet.</string>
|
||||
@@ -195,7 +194,7 @@
|
||||
<string name="sign">Sign</string>
|
||||
<string name="sign_in">Sign in</string>
|
||||
<string name="sign_in_to_nsec">Sign in to nsec</string>
|
||||
<string name="sign_in_to_torch_via_nsec_or_remote_signer">Sign in to Torch via nsec, or remote signer</string>
|
||||
<string name="sign_in_to_torch_via_nsec_or_remote_signer">Sign in to Mantra via nsec, or remote signer</string>
|
||||
<string name="sign_in_with_an_npub">Sign in with an npub</string>
|
||||
<string name="sign_out">Sign out</string>
|
||||
<string name="sign_with_the_group_s_key">Sign with the group's key</string>
|
||||
@@ -234,7 +233,7 @@
|
||||
<string name="this_will_be_the_display_name_for_your">This will be the display name for your profile and also important for search.</string>
|
||||
<string name="this_will_give_you_read_only_access_to_the">This will give you read only access to the profile.</string>
|
||||
<string name="this_will_give_you_write_access_to_the">This will give you write access to the profile.</string>
|
||||
<string name="torch_will_be_broadcast_what_you_publish_to">Torch will be broadcast what you publish to a distributed set of relays so that it's decentralized.</string>
|
||||
<string name="torch_will_be_broadcast_what_you_publish_to">Mantra broadcasts what you publish to a distributed set of relays, so it stays decentralised.</string>
|
||||
<string name="translate_chunk">Translate chunk</string>
|
||||
<string name="translate_into_which_dialect">Translate into which dialect?</string>
|
||||
<string name="translated_text">Translated text</string>
|
||||
@@ -286,7 +285,7 @@
|
||||
<string name="functionality_coming_soon_2">"%1$s" functionality coming soon</string>
|
||||
<string name="how_should_be_run">How should %1$s be run?</string>
|
||||
<string name="invite_2">Invite %1$s</string>
|
||||
<string name="key_packages">%1$s Key Packages</string>
|
||||
<string name="key_packages">%1$s key packages</string>
|
||||
<string name="next_with">Next with %1$s</string>
|
||||
<string name="nostr">nostr:%1$s..</string>
|
||||
<string name="nostr_2">nostr:%1$s...</string>
|
||||
@@ -301,7 +300,7 @@
|
||||
<string name="recovered_of_still_unreadable">Recovered %1$s of %2$s · %3$s still unreadable%4$s</string>
|
||||
<string name="reply_privately_to">Reply privately to %1$s</string>
|
||||
<string name="reply_to">Reply to %1$s</string>
|
||||
<string name="replying_to">replying To %1$s</string>
|
||||
<string name="replying_to">Replying to %1$s</string>
|
||||
<string name="searching_for_on">Searching for %1$s on "%2$s"</string>
|
||||
<string name="selected">%1$s selected</string>
|
||||
<string name="sent_a_private_message_to">%1$s sent a private message to %2$s</string>
|
||||
@@ -309,8 +308,15 @@
|
||||
<string name="to_join_the_chat_room">to join the %1$s chat room.</string>
|
||||
<string name="translate">Translate %1$s</string>
|
||||
<string name="unsupported_event_kind_2">Unsupported event kind: %1$s</string>
|
||||
<string name="was_created_but_couldn_t_be_added_yet_invite">%1$s was created, but %2$s couldn't be added yet. Invite them again from the chat once they're on Torch.</string>
|
||||
<string name="was_created_but_couldn_t_be_added_yet_invite">%1$s was created, but %2$s couldn't be added yet. Invite them again from the chat once they're on Mantra.</string>
|
||||
<string name="was_created_but_its_shared_key_ceremony">%1$s was created, but its shared key ceremony couldn't be started. Open the chat and start it from the group's details — until then the group has no key of its own.</string>
|
||||
<string name="words_characters">%1$s words · %2$s characters</string>
|
||||
<string name="you_and_others">You and %1$s others</string>
|
||||
<string name="not_following_anyone_yet">You aren't following anyone yet.</string>
|
||||
<string name="nobody_following_you_yet">Nobody is following you yet.</string>
|
||||
<string name="nothing_in_this_feed_yet">Nothing in this feed yet.</string>
|
||||
<string name="no_replies_to_this_yet">No replies to this yet.</string>
|
||||
<string name="nothing_matched_that_search">Nothing matched that search.</string>
|
||||
<string name="key_package_published">Key package published</string>
|
||||
<string name="key_package_rotated">Key package rotated</string>
|
||||
</resources>
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavHostController
|
||||
import press.mantra.compose.ui.composable.navigation.MantraNavHost
|
||||
import press.mantra.compose.ui.composable.widgets.ProvideSnackbarHost
|
||||
import press.mantra.compose.ui.theme.TorchTheme
|
||||
import fr.acinq.phoenix.PhoenixGlobal
|
||||
|
||||
@@ -19,11 +20,18 @@ fun MantraApp(
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
MantraNavHost(
|
||||
mantraGlobal = mantraGlobal,
|
||||
phoenixGlobal = phoenixGlobal,
|
||||
navController = navController
|
||||
)
|
||||
// One host state for the whole app rather than one per screen. Only one
|
||||
// Scaffold is composed at a time under a NavHost, so the message renders in
|
||||
// whichever screen is on top -- and a view model coroutine reporting an
|
||||
// outcome does not have to be handed a state through the parameter list of
|
||||
// every composable between it and the Scaffold.
|
||||
ProvideSnackbarHost {
|
||||
MantraNavHost(
|
||||
mantraGlobal = mantraGlobal,
|
||||
phoenixGlobal = phoenixGlobal,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -64,6 +64,9 @@ import mantra.composeapp.generated.resources.share_profile
|
||||
import mantra.composeapp.generated.resources.sign_out
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.we_couldn_t_find_the_local_profile_please
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -87,16 +90,11 @@ fun ActiveProfileScreen(
|
||||
|
||||
when(val activeProfileUIState = activeProfileViewModel.activeProfileUIState) {
|
||||
ActiveProfileUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(stringResource(Res.string.something_went_wrong))
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is ActiveProfileUIState.Loaded -> {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
+3
@@ -83,6 +83,8 @@ import mantra.composeapp.generated.resources.no_dialects_have_been_defined_in_th
|
||||
import mantra.composeapp.generated.resources.propose_artifact
|
||||
import mantra.composeapp.generated.resources.source_dialect
|
||||
import mantra.composeapp.generated.resources.url
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
@@ -148,6 +150,7 @@ fun AddArtifactScreen(
|
||||
// imePadding: this screen has a text field, and without it the software
|
||||
// keyboard covers whatever is being typed into.
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.imePadding(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
||||
+3
@@ -64,6 +64,8 @@ import mantra.composeapp.generated.resources.paste_the_chapter_s_markdown_blank_
|
||||
import mantra.composeapp.generated.resources.propose_chapter
|
||||
import mantra.composeapp.generated.resources.this_artifact_has_no_version_for_a_chapter
|
||||
import mantra.composeapp.generated.resources.add_chapter_to
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -136,6 +138,7 @@ fun AddChapterScreen(
|
||||
// imePadding: this screen has a text field, and without it the software
|
||||
// keyboard covers whatever is being typed into.
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.imePadding(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
||||
+3
@@ -67,6 +67,8 @@ import mantra.composeapp.generated.resources.eg_sesotho
|
||||
import mantra.composeapp.generated.resources.eg_st
|
||||
import mantra.composeapp.generated.resources.language
|
||||
import mantra.composeapp.generated.resources.propose_dialect
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -119,6 +121,7 @@ fun AddDialectScreen(
|
||||
// imePadding: this screen has a text field, and without it the software
|
||||
// keyboard covers whatever is being typed into.
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.imePadding(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
||||
+3
@@ -46,6 +46,8 @@ import mantra.composeapp.generated.resources.invite
|
||||
import mantra.composeapp.generated.resources.invite_2
|
||||
import mantra.composeapp.generated.resources.once_invited_will_be_able_to_receive_and
|
||||
import mantra.composeapp.generated.resources.to_join_the_chat_room
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -88,6 +90,7 @@ fun AddMemberToChatRoomConfirmationScreen(
|
||||
}
|
||||
is AddMemberToChatRoomConfirmationUIState.Loaded -> {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
// topBar = {
|
||||
// TopAppBar(
|
||||
// title = {
|
||||
|
||||
+3
@@ -65,6 +65,8 @@ import mantra.composeapp.generated.resources.propose_translation
|
||||
import mantra.composeapp.generated.resources.this_artifact_has_no_version_for_a
|
||||
import mantra.composeapp.generated.resources.translate_into_which_dialect
|
||||
import mantra.composeapp.generated.resources.translate
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
@@ -136,6 +138,7 @@ fun AddTranslationArtifactVersionScreen(
|
||||
val buttonColors = ButtonDefaults.buttonColors()
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(Res.string.translate, addTranslationUIState.artifact.name)) },
|
||||
|
||||
+3
@@ -64,6 +64,8 @@ import mantra.composeapp.generated.resources.version
|
||||
import mantra.composeapp.generated.resources.versions
|
||||
import mantra.composeapp.generated.resources.chapter
|
||||
import mantra.composeapp.generated.resources.words_characters
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -102,6 +104,7 @@ fun ArtifactDetailScreen(
|
||||
is ArtifactDetailUIState.Loaded -> {
|
||||
val artifact = artifactDetailUIState.artifact
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(artifact.name) },
|
||||
|
||||
+3
@@ -48,6 +48,8 @@ import mantra.composeapp.generated.resources.chapter_words_characters
|
||||
import mantra.composeapp.generated.resources.chunk
|
||||
import mantra.composeapp.generated.resources.chunks
|
||||
import mantra.composeapp.generated.resources.words_characters
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -85,6 +87,7 @@ fun ChapterDetailScreen(
|
||||
is ChapterDetailUIState.Loaded -> {
|
||||
val chapter = chapterDetailUIState.chapter
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(chapter.name) },
|
||||
|
||||
+5
-1
@@ -39,6 +39,8 @@ import mantra.composeapp.generated.resources.this_will_be_shown_when_people_open
|
||||
import mantra.composeapp.generated.resources.this_will_be_the_display_name_for_this_chat
|
||||
import mantra.composeapp.generated.resources.what_should_people_know_about_you
|
||||
import mantra.composeapp.generated.resources.what_will_be_discussed_in_this_chat_room
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@Composable
|
||||
fun ChatRoomCreationScreen(
|
||||
@@ -51,7 +53,9 @@ fun ChatRoomCreationScreen(
|
||||
|
||||
// imePadding: this screen has a text field, and without it the software
|
||||
// keyboard covers whatever is being typed into.
|
||||
Scaffold(modifier = Modifier.imePadding()) { innerPadding ->
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.imePadding()) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxWidth()
|
||||
) {
|
||||
|
||||
+3
@@ -92,6 +92,8 @@ import mantra.composeapp.generated.resources.shared_key
|
||||
import mantra.composeapp.generated.resources.event_s_still_unreadable
|
||||
import mantra.composeapp.generated.resources.recovered_of_event_s
|
||||
import mantra.composeapp.generated.resources.recovered_of_still_unreadable
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -134,6 +136,7 @@ fun ChatRoomDetailScreen(
|
||||
}
|
||||
is ChatRoomDetailUIState.Loaded -> {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
+3
@@ -66,6 +66,8 @@ import mantra.composeapp.generated.resources.direct_message_functionality_will_b
|
||||
import mantra.composeapp.generated.resources.say_what_now
|
||||
import mantra.composeapp.generated.resources.private_message_to
|
||||
import mantra.composeapp.generated.resources.private_to
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -118,6 +120,7 @@ fun ChatRoomMessagingScreen(
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
+5
-1
@@ -59,6 +59,8 @@ import mantra.composeapp.generated.resources.this_will_be_the_display_name_for_y
|
||||
import mantra.composeapp.generated.resources.what_should_people_know_about_you
|
||||
import mantra.composeapp.generated.resources.you_are_about_to_create_a_nostr_profile
|
||||
import mantra.composeapp.generated.resources.you_will_be_in_full_control_of_this_profile
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -78,7 +80,9 @@ fun CreateProfileScreen(
|
||||
)
|
||||
// imePadding: this screen has a text field, and without it the software
|
||||
// keyboard covers whatever is being typed into.
|
||||
Scaffold(modifier = Modifier.imePadding()) { innerPadding ->
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.imePadding()) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+3
@@ -43,6 +43,8 @@ import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.back
|
||||
import mantra.composeapp.generated.resources.not_now
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
/**
|
||||
* The frame every ChillDKG approval screen sits in: the view model, the states
|
||||
@@ -84,6 +86,7 @@ internal fun DkgApprovalScaffold(
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(text = title, maxLines = 1, overflow = TextOverflow.Ellipsis) }
|
||||
|
||||
@@ -99,6 +99,8 @@ import mantra.composeapp.generated.resources.everyone_has_to_be_online_at_the_sa
|
||||
import mantra.composeapp.generated.resources.of
|
||||
import mantra.composeapp.generated.resources.of_members_will_be_needed_to_sign_with_this
|
||||
import mantra.composeapp.generated.resources.shared_key_for
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
/**
|
||||
* The shared-key ceremony: a ChillDKG ritual run across the group's NIP-17
|
||||
@@ -148,6 +150,7 @@ fun DkgRitualScreen(
|
||||
val isActionPending = dkgRitualViewModel.isActionPending.value
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
+3
@@ -69,6 +69,8 @@ import mantra.composeapp.generated.resources.sign
|
||||
import mantra.composeapp.generated.resources.sign_with_the_group_s_key
|
||||
import mantra.composeapp.generated.resources.signed_their_part
|
||||
import mantra.composeapp.generated.resources.events_signed_together
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
/**
|
||||
* One signing session, and the member's decision about it.
|
||||
@@ -112,6 +114,7 @@ fun FrostSigningScreen(
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
@@ -62,6 +62,9 @@ import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.mantra
|
||||
import mantra.composeapp.generated.resources.new_chat
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -91,28 +94,19 @@ fun HomeScreen(
|
||||
|
||||
when(val homeScreenUIState = homeScreenViewModel.homeScreenUIState) {
|
||||
HomeScreenUIState.Error -> {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong)
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier.weight(2f)
|
||||
)
|
||||
ErrorState()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
is HomeScreenUIState.Loaded -> {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier,
|
||||
topBar = {
|
||||
// No colour override. It was `containerColor = primaryContainer` with
|
||||
|
||||
+3
-1
@@ -17,13 +17,15 @@ import press.mantra.compose.ui.theme.spacing
|
||||
import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.functionality_coming_soon_2
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@Composable
|
||||
fun ImplementationPendingScreen(
|
||||
text: String
|
||||
) {
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
) {
|
||||
|
||||
+21
-9
@@ -51,6 +51,13 @@ import mantra.composeapp.generated.resources.publish_new_key_package
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.we_couldn_t_find_the_local_profile_please
|
||||
import mantra.composeapp.generated.resources.key_packages
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import press.mantra.compose.ui.composable.widgets.rememberNotifier
|
||||
import mantra.composeapp.generated.resources.key_package_published
|
||||
import mantra.composeapp.generated.resources.key_package_rotated
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -63,6 +70,12 @@ fun KeyPackageManagementScreen(
|
||||
marmotRepository: MarmotRepository
|
||||
) {
|
||||
|
||||
// Read outside the click handlers: stringResource and rememberNotifier are both
|
||||
// composable, and a lambda passed to onClick is not.
|
||||
val notify = rememberNotifier(rememberCoroutineScope())
|
||||
val publishedMessage = stringResource(Res.string.key_package_published)
|
||||
val rotatedMessage = stringResource(Res.string.key_package_rotated)
|
||||
|
||||
val keyPackageManagementViewModel: KeyPackageManagementViewModel = viewModel(
|
||||
factory = KeyPackageManagementViewModel.factory(
|
||||
nostrEventId = nostrEventId,
|
||||
@@ -74,16 +87,11 @@ fun KeyPackageManagementScreen(
|
||||
|
||||
when(val keyPackageManagementUIState = keyPackageManagementViewModel.keyPackageManagementUIState) {
|
||||
KeyPackageManagementUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(stringResource(Res.string.something_went_wrong))
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is KeyPackageManagementUIState.Loaded -> {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
@@ -117,7 +125,9 @@ fun KeyPackageManagementScreen(
|
||||
item {
|
||||
Button(
|
||||
onClick = {
|
||||
keyPackageManagementViewModel.publishNewKeyPackage()
|
||||
keyPackageManagementViewModel.publishNewKeyPackage {
|
||||
notify(publishedMessage)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
@@ -193,7 +203,9 @@ fun KeyPackageManagementScreen(
|
||||
onClick = {
|
||||
keyPackageManagementViewModel.rotateKeyPackage(
|
||||
marmotKeyPackageBundle
|
||||
)
|
||||
) {
|
||||
notify(rotatedMessage)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
|
||||
+3
-1
@@ -29,6 +29,8 @@ import mantra.composeapp.generated.resources.learn_more
|
||||
import mantra.composeapp.generated.resources.mantra
|
||||
import mantra.composeapp.generated.resources.sign_in
|
||||
import mantra.composeapp.generated.resources.sign_in_to_torch_via_nsec_or_remote_signer
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@Composable
|
||||
fun LandingScreen(
|
||||
@@ -36,7 +38,7 @@ fun LandingScreen(
|
||||
onNavigateToSignIn: () -> Unit,
|
||||
onNavigateToCreateProfile: () -> Unit
|
||||
) {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+3
-1
@@ -8,12 +8,14 @@ import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@Composable
|
||||
fun LoadingScreen(
|
||||
text: String? = null
|
||||
) {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+5
-8
@@ -38,6 +38,9 @@ import mantra.composeapp.generated.resources.functionality_coming_soon
|
||||
import mantra.composeapp.generated.resources.post_functionality_coming_soon
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.we_couldn_t_find_your_nostr_event_please_try
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -64,13 +67,7 @@ fun NostrEventDetailScreen(
|
||||
|
||||
when(val feedListUIState = nostrEventDetailViewModel.nostrEventDetailUIState) {
|
||||
NostrEventDetailUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(stringResource(Res.string.something_went_wrong))
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is NostrEventDetailUIState.Loaded -> {
|
||||
|
||||
@@ -108,7 +105,7 @@ fun NostrEventDetailScreen(
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
) {
|
||||
|
||||
+3
@@ -63,6 +63,8 @@ import mantra.composeapp.generated.resources.review
|
||||
import mantra.composeapp.generated.resources.this_group_has_not_been_asked_to_sign
|
||||
import mantra.composeapp.generated.resources.waiting_for_you
|
||||
import mantra.composeapp.generated.resources.of_them_could_not_be_read
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
/**
|
||||
* Everything the group has asked its shared key to sign.
|
||||
@@ -106,6 +108,7 @@ fun ProposalListScreen(
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
+3
@@ -46,6 +46,8 @@ import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.currently_no_contacts_please_search_and_chat
|
||||
import mantra.composeapp.generated.resources.search_member_functionality
|
||||
import mantra.composeapp.generated.resources.search_message_functionality_will_be_here
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -85,6 +87,7 @@ fun SearchMemberToAddToChatRoomScreen(
|
||||
}
|
||||
is SearchMemberToAddToChatRoomUIState.Loaded -> {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
+8
-24
@@ -57,11 +57,15 @@ import kotlinx.coroutines.launch
|
||||
import press.mantra.compose.ui.theme.spacing
|
||||
import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.no_events_were_found
|
||||
import mantra.composeapp.generated.resources.profiles
|
||||
import mantra.composeapp.generated.resources.search
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.searching_for_on
|
||||
import mantra.composeapp.generated.resources.nothing_matched_that_search
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import press.mantra.compose.ui.composable.widgets.EmptyState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -89,7 +93,7 @@ fun SearchResultScreen(
|
||||
var expanded by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
@@ -213,32 +217,12 @@ fun SearchResultScreen(
|
||||
|
||||
when (val searchResultListUIState = searchViewModel.searchResultListUIState) {
|
||||
SearchResultListUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong),
|
||||
)
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is SearchResultListUIState.Loaded -> {
|
||||
// TODO: Handle gallery UI
|
||||
if (searchResultListUIState.localNostrEvents.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.no_events_were_found),
|
||||
)
|
||||
}
|
||||
EmptyState(message = stringResource(Res.string.nothing_matched_that_search))
|
||||
} else {
|
||||
|
||||
LazyColumn(
|
||||
|
||||
@@ -70,6 +70,8 @@ import mantra.composeapp.generated.resources.recents
|
||||
import mantra.composeapp.generated.resources.search
|
||||
import mantra.composeapp.generated.resources.search_hashtags
|
||||
import mantra.composeapp.generated.resources.trending_notes_functionality_coming_soon_in
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -92,7 +94,7 @@ fun SearchScreen(
|
||||
// Controls expansion state of the search bar
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
|
||||
+3
@@ -53,6 +53,8 @@ import mantra.composeapp.generated.resources.you_can_still_carry_on_and_invite_p
|
||||
import mantra.composeapp.generated.resources.add_people_to
|
||||
import mantra.composeapp.generated.resources.next_with
|
||||
import mantra.composeapp.generated.resources.selected
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
/**
|
||||
* Second step of group creation: pick who is in the group.
|
||||
@@ -100,6 +102,7 @@ fun SelectChatRoomMembersScreen(
|
||||
val selectedCount = selectChatRoomMembersViewModel.selectedPublicKeys.size
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
+3
@@ -70,6 +70,8 @@ import mantra.composeapp.generated.resources.of
|
||||
import mantra.composeapp.generated.resources.was_created_but_couldn_t_be_added_yet_invite
|
||||
import mantra.composeapp.generated.resources.was_created_but_its_shared_key_ceremony
|
||||
import mantra.composeapp.generated.resources.you_and_others
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
/**
|
||||
* Last step of group creation: convenient (one admin) or robust (everyone
|
||||
@@ -128,6 +130,7 @@ fun SelectChatRoomTypeScreen(
|
||||
val isRobustSelected = selectedChatRoomType == ChatRoomType.ROBUST
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
+5
-7
@@ -51,6 +51,9 @@ import mantra.composeapp.generated.resources.profile
|
||||
import mantra.composeapp.generated.resources.re_broadcast
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.we_couldn_t_find_the_local_profile_please
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -74,16 +77,11 @@ fun ShareProfileScreen(
|
||||
|
||||
when(val shareProfileUIState = shareProfileViewModel.shareProfileUIState) {
|
||||
ShareProfileUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(stringResource(Res.string.something_went_wrong))
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is ShareProfileUIState.Loaded -> {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
||||
@@ -44,6 +44,8 @@ import mantra.composeapp.generated.resources.sign_in_with_an_npub
|
||||
import mantra.composeapp.generated.resources.something_went_wrong_and_we_were_unable_to
|
||||
import mantra.composeapp.generated.resources.this_will_give_you_read_only_access_to_the
|
||||
import mantra.composeapp.generated.resources.this_will_give_you_write_access_to_the
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -59,7 +61,9 @@ fun SignInToProfileScreen(
|
||||
)
|
||||
// imePadding: this screen has a text field, and without it the software
|
||||
// keyboard covers whatever is being typed into.
|
||||
Scaffold(modifier = Modifier.imePadding()) { innerPadding ->
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.imePadding()) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+3
@@ -31,6 +31,8 @@ import mantra.composeapp.generated.resources.tell_friends_to_join_you_so_your_fe
|
||||
import mantra.composeapp.generated.resources.view_and_accept_invites_you_may_have
|
||||
import mantra.composeapp.generated.resources.view_invites
|
||||
import mantra.composeapp.generated.resources.who_will_you_be_passing_the_aux_to
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@Composable
|
||||
fun SocialPreconditionScreen(
|
||||
@@ -39,6 +41,7 @@ fun SocialPreconditionScreen(
|
||||
onNavigateToViewInvites: () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
bottomBar = {
|
||||
BottomAppBar(
|
||||
modifier = Modifier,
|
||||
|
||||
+3
@@ -62,6 +62,8 @@ import mantra.composeapp.generated.resources.propose_translation
|
||||
import mantra.composeapp.generated.resources.translate_chunk
|
||||
import mantra.composeapp.generated.resources.translated_text
|
||||
import mantra.composeapp.generated.resources.translation
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -114,6 +116,7 @@ fun TranslateChunkScreen(
|
||||
// imePadding: this screen has a text field, and without it the software
|
||||
// keyboard covers whatever is being typed into.
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.imePadding(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
||||
+3
@@ -57,6 +57,8 @@ import mantra.composeapp.generated.resources.this_group_has_no_shared_key_so_it_
|
||||
import mantra.composeapp.generated.resources.translation_detail
|
||||
import mantra.composeapp.generated.resources.chapter
|
||||
import mantra.composeapp.generated.resources.chunks_translated
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -99,6 +101,7 @@ fun TranslationArtifactVersionDetailScreen(
|
||||
is TranslationArtifactVersionDetailUIState.Loaded -> {
|
||||
val translation = translationDetailUIState.translation
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(translation.name) },
|
||||
|
||||
+3
@@ -52,6 +52,8 @@ import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.chapter_translation
|
||||
import mantra.composeapp.generated.resources.this_chapter_has_no_chunks
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -89,6 +91,7 @@ fun TranslationChapterScreen(
|
||||
|
||||
is TranslationChapterUIState.Loaded -> {
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(Res.string.chapter_translation)) },
|
||||
|
||||
+3
-1
@@ -22,11 +22,13 @@ import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.everything_is_cryptographical_sound_just
|
||||
import mantra.composeapp.generated.resources.torch_will_be_broadcast_what_you_publish_to
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun UnannouncedProfileScreen() {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+3
-1
@@ -22,11 +22,13 @@ import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.events_are_indexed_so_that_we_can_deliver_a
|
||||
import mantra.composeapp.generated.resources.everything_is_cryptographical_sound_just_2
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun UnindexedProfileScreen() {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+3
-1
@@ -22,11 +22,13 @@ import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.all_broadcasts_are_queued_so_that_we_can
|
||||
import mantra.composeapp.generated.resources.everything_is_cryptographical_sound_just_3
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun UnqueuedProfileScreen() {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+3
-1
@@ -24,6 +24,8 @@ import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.we_are_looking_for_your_profile_on_as_many
|
||||
import mantra.composeapp.generated.resources.we_are_searching_the_internet_to_find_your
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -40,7 +42,7 @@ fun UnqueuedProfileSynchronizationScreen(
|
||||
)
|
||||
)
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+3
-1
@@ -22,11 +22,13 @@ import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.as_long_as_you_control_your_keys_there_can
|
||||
import mantra.composeapp.generated.resources.your_profile_is_almost_ready_just_getting_it
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun UnsignedProfileScreen() {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+3
-1
@@ -22,13 +22,15 @@ import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.we_are_looking_for_your_profile_on_as_many
|
||||
import mantra.composeapp.generated.resources.we_are_searching_the_internet_to_find_your
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun UnsyncedProfileScreen(
|
||||
unsyncedProfilePublicKey: String
|
||||
) {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+5
-1
@@ -56,6 +56,8 @@ import mantra.composeapp.generated.resources.what_s_your_comment_on_the_below
|
||||
import mantra.composeapp.generated.resources.what_s_your_reply_to_the_above
|
||||
import mantra.composeapp.generated.resources.what_vibrations_do_you_want_to_send_out
|
||||
import mantra.composeapp.generated.resources.replying_to
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -79,7 +81,9 @@ fun WriteNewNoteScreen(
|
||||
)
|
||||
// imePadding: this screen has a text field, and without it the software
|
||||
// keyboard covers whatever is being typed into.
|
||||
Scaffold(modifier = Modifier.imePadding()) { innerPadding ->
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.imePadding()) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding)
|
||||
) {
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package press.mantra.compose.ui.composable.widgets
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ErrorOutline
|
||||
import androidx.compose.material.icons.filled.Inbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import mantra.composeapp.generated.resources.Res
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.try_again
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import press.mantra.compose.ui.theme.spacing
|
||||
|
||||
/**
|
||||
* The two states every list and every screen has, and neither of which had a home.
|
||||
*
|
||||
* Before this file the tree held **16 copies** of
|
||||
*
|
||||
* Column(horizontalAlignment = CenterHorizontally) {
|
||||
* Spacer(Modifier.height(48.dp))
|
||||
* Text("Something went wrong")
|
||||
* }
|
||||
*
|
||||
* and five of the same shape saying "No events were found". Not one of the sixteen offered
|
||||
* a retry, so every failure in this app was a dead end: the message named no cause, and
|
||||
* the only way out was the back button.
|
||||
*
|
||||
* M3's content guidance asks the opposite -- "emphasize the results of the user's
|
||||
* potential action", "tell users what will happen ... and how they can undo it" -- and its
|
||||
* structure guidance asks that a screen say what to do next rather than only what
|
||||
* happened.
|
||||
*
|
||||
* These two composables are deliberately plain: an icon, a line, and for [ErrorState] an
|
||||
* action when the caller can offer one. They are the thing 21 call sites collapse into,
|
||||
* not a design in their own right.
|
||||
*/
|
||||
@Composable
|
||||
fun ErrorState(
|
||||
modifier: Modifier = Modifier,
|
||||
message: String = stringResource(Res.string.something_went_wrong),
|
||||
/**
|
||||
* `null` where the caller genuinely has nothing to retry -- a screen whose state came
|
||||
* from a navigation argument that was already wrong. Passing null is a decision;
|
||||
* omitting a retry that exists is the thing this parameter is here to make visible.
|
||||
*/
|
||||
onRetry: (() -> Unit)? = null,
|
||||
) {
|
||||
StateMessage(
|
||||
modifier = modifier,
|
||||
icon = Icons.Default.ErrorOutline,
|
||||
message = message,
|
||||
// The icon is decorative: the message beside it says what happened, and naming
|
||||
// this one would have a screen reader announce the trouble twice.
|
||||
iconDescription = Decorative,
|
||||
action = onRetry?.let {
|
||||
{ TextButton(onClick = it) { Text(stringResource(Res.string.try_again)) } }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A list with nothing in it yet.
|
||||
*
|
||||
* The message is required rather than defaulted, because "No events were found" told the
|
||||
* user nothing about which list was empty or what would fill it, and a shared default
|
||||
* would preserve exactly that. Say what is missing and, where there is one, how to get
|
||||
* some.
|
||||
*/
|
||||
@Composable
|
||||
fun EmptyState(
|
||||
message: String,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: ImageVector = Icons.Default.Inbox,
|
||||
action: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
StateMessage(
|
||||
modifier = modifier,
|
||||
icon = icon,
|
||||
message = message,
|
||||
iconDescription = Decorative,
|
||||
action = action,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StateMessage(
|
||||
modifier: Modifier,
|
||||
icon: ImageVector,
|
||||
message: String,
|
||||
iconDescription: String?,
|
||||
action: (@Composable () -> Unit)?,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = MaterialTheme.spacing.screenMargin,
|
||||
vertical = MaterialTheme.spacing.emphasisGap,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.itemGap),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = iconDescription,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Text(
|
||||
text = message,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
action?.invoke()
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package press.mantra.compose.ui.composable.widgets
|
||||
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Somewhere to say what just happened.
|
||||
*
|
||||
* The app had **no** `Snackbar`, `SnackbarHost` or `SnackbarHostState` anywhere, across 26
|
||||
* `Scaffold`s. Every transient outcome -- an invite that failed, a key package published,
|
||||
* a message that did not send -- had nowhere to be reported, so the code either said
|
||||
* nothing or navigated away and hoped.
|
||||
*
|
||||
* M3 puts a snackbar host in the `Scaffold` for this, and the reason it is a composition
|
||||
* local rather than a parameter is where the reporting happens: a view model coroutine
|
||||
* finishing a network call is several composables below the `Scaffold` that owns the host.
|
||||
* Threading a `SnackbarHostState` down through every screen's parameter list would be the
|
||||
* same plumbing repeated 26 times, and the parameter would be forgotten on the 27th.
|
||||
*
|
||||
* ```
|
||||
* Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { … }
|
||||
* …
|
||||
* val notify = rememberNotifier()
|
||||
* notify("Invite sent")
|
||||
* ```
|
||||
*/
|
||||
val LocalSnackbarHostState: ProvidableCompositionLocal<SnackbarHostState> =
|
||||
staticCompositionLocalOf {
|
||||
error(
|
||||
"No SnackbarHostState. Wrap the screen in ProvideSnackbarHost, or hand the " +
|
||||
"Scaffold a snackbarHost of its own."
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a host state for everything inside [content].
|
||||
*
|
||||
* Deliberately throws rather than defaulting to a detached `SnackbarHostState()`. A
|
||||
* default would make `notify(...)` a silent no-op on any screen that forgot the host,
|
||||
* which is the failure this whole file exists to end -- a message with nowhere to go is
|
||||
* exactly what the app already had.
|
||||
*/
|
||||
@Composable
|
||||
fun ProvideSnackbarHost(
|
||||
hostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||
content: @Composable (SnackbarHostState) -> Unit,
|
||||
) {
|
||||
CompositionLocalProvider(LocalSnackbarHostState provides hostState) {
|
||||
content(hostState)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A function for showing a message, callable from anywhere under a [ProvideSnackbarHost].
|
||||
*
|
||||
* Takes the scope from the caller so the message survives the composable that sent it
|
||||
* going away -- which it usually does, since "saved" is shown as the screen navigates
|
||||
* back.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberNotifier(scope: CoroutineScope): (String) -> Unit {
|
||||
val hostState = LocalSnackbarHostState.current
|
||||
return remember(hostState, scope) {
|
||||
{ message ->
|
||||
scope.launch {
|
||||
// Short by default. A snackbar is for something the user does not have to
|
||||
// act on; anything they must read belongs in a dialog or on the screen.
|
||||
hostState.showSnackbar(message = message, duration = SnackbarDuration.Short)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -67,6 +67,8 @@ import mantra.composeapp.generated.resources.follow
|
||||
import mantra.composeapp.generated.resources.follow_back
|
||||
import mantra.composeapp.generated.resources.send_message
|
||||
import mantra.composeapp.generated.resources.unfollow
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -94,6 +96,7 @@ fun MetadataEventDetail(
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
LargeTopAppBar(
|
||||
|
||||
+5
-5
@@ -55,6 +55,9 @@ import mantra.composeapp.generated.resources.be_the_first_to_comment
|
||||
import mantra.composeapp.generated.resources.post
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.reply_to
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -79,6 +82,7 @@ fun TextNoteEventDetail(
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) },
|
||||
modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -367,11 +371,7 @@ fun TextNoteEventDetail(
|
||||
}
|
||||
}
|
||||
press.mantra.compose.ui.view.state.FeedListUIState.Error -> {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong)
|
||||
)
|
||||
}
|
||||
item { ErrorState() }
|
||||
}
|
||||
press.mantra.compose.ui.view.state.FeedListUIState.Loading -> {
|
||||
item {
|
||||
|
||||
+2
-11
@@ -108,6 +108,7 @@ import mantra.composeapp.generated.resources.private_to
|
||||
import mantra.composeapp.generated.resources.proposals_are_waiting_for_your_signature
|
||||
import mantra.composeapp.generated.resources.reply_privately_to
|
||||
import mantra.composeapp.generated.resources.sent_a_private_message_to
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
|
||||
class ChatMessageListViewModel(
|
||||
initialChatMessageListUIState: ChatMessageListUIState,
|
||||
@@ -405,17 +406,7 @@ class ChatMessageListViewModel(
|
||||
) {
|
||||
when (val chatRoomDetailMessageListUIState = this@ChatMessageListViewModel.chatMessageListUIState) {
|
||||
ChatMessageListUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong),
|
||||
)
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is ChatMessageListUIState.Loaded -> {
|
||||
Spacer(
|
||||
|
||||
+2
-11
@@ -46,6 +46,7 @@ import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.loading
|
||||
import mantra.composeapp.generated.resources.no_messages_go_to_a_profile_and_send_them_a
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
|
||||
class ChatRoomListViewModel(
|
||||
initialChatRoomListUIState: ChatRoomListUIState,
|
||||
@@ -151,17 +152,7 @@ class ChatRoomListViewModel(
|
||||
) {
|
||||
when (val chatRoomListUIState = chatRoomListUIState) {
|
||||
ChatRoomListUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong),
|
||||
)
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is ChatRoomListUIState.Loaded -> {
|
||||
Column(
|
||||
|
||||
+5
-23
@@ -38,8 +38,10 @@ import press.mantra.compose.ui.theme.spacing
|
||||
import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.loading
|
||||
import mantra.composeapp.generated.resources.no_events_were_found
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.nothing_in_this_feed_yet
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import press.mantra.compose.ui.composable.widgets.EmptyState
|
||||
|
||||
class FeedListViewModel(
|
||||
initialFeedListUIState: FeedListUIState,
|
||||
@@ -118,31 +120,11 @@ class FeedListViewModel(
|
||||
) {
|
||||
when (val feedListUIState = feedListUIState) {
|
||||
FeedListUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong),
|
||||
)
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is FeedListUIState.Loaded -> {
|
||||
if (feedListUIState.localNostrEvents.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.no_events_were_found),
|
||||
)
|
||||
}
|
||||
EmptyState(message = stringResource(Res.string.nothing_in_this_feed_yet))
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
+5
-23
@@ -34,8 +34,10 @@ import press.mantra.compose.ui.theme.spacing
|
||||
import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.loading
|
||||
import mantra.composeapp.generated.resources.no_events_were_found
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.nobody_following_you_yet
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import press.mantra.compose.ui.composable.widgets.EmptyState
|
||||
|
||||
class FollowersListViewModel(
|
||||
initialFollowingListUIState: press.mantra.compose.ui.view.state.FollowersListUIState,
|
||||
@@ -112,31 +114,11 @@ class FollowersListViewModel(
|
||||
) {
|
||||
when (val followersListUIState = followersListUIState) {
|
||||
press.mantra.compose.ui.view.state.FollowersListUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong),
|
||||
)
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is press.mantra.compose.ui.view.state.FollowersListUIState.Loaded -> {
|
||||
if (followersListUIState.followers.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.no_events_were_found),
|
||||
)
|
||||
}
|
||||
EmptyState(message = stringResource(Res.string.nobody_following_you_yet))
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
+5
-23
@@ -34,8 +34,10 @@ import press.mantra.compose.ui.theme.spacing
|
||||
import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.loading
|
||||
import mantra.composeapp.generated.resources.no_events_were_found
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.not_following_anyone_yet
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import press.mantra.compose.ui.composable.widgets.EmptyState
|
||||
|
||||
class FollowingListViewModel(
|
||||
initialFollowingListUIState: press.mantra.compose.ui.view.state.FollowingListUIState,
|
||||
@@ -111,31 +113,11 @@ class FollowingListViewModel(
|
||||
) {
|
||||
when (val feedListUIState = followingListUIState) {
|
||||
press.mantra.compose.ui.view.state.FollowingListUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong),
|
||||
)
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is press.mantra.compose.ui.view.state.FollowingListUIState.Loaded -> {
|
||||
if (feedListUIState.following.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.no_events_were_found),
|
||||
)
|
||||
}
|
||||
EmptyState(message = stringResource(Res.string.not_following_anyone_yet))
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
+5
-23
@@ -39,8 +39,10 @@ import press.mantra.compose.ui.theme.spacing
|
||||
import mantra.composeapp.generated.resources.Res
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import mantra.composeapp.generated.resources.loading
|
||||
import mantra.composeapp.generated.resources.no_events_were_found
|
||||
import mantra.composeapp.generated.resources.something_went_wrong
|
||||
import mantra.composeapp.generated.resources.no_replies_to_this_yet
|
||||
import press.mantra.compose.ui.composable.widgets.ErrorState
|
||||
import press.mantra.compose.ui.composable.widgets.EmptyState
|
||||
|
||||
class InReplyToViewModel(
|
||||
val nostrEvent: press.mantra.compose.database.model.NostrEvent,
|
||||
@@ -122,31 +124,11 @@ class InReplyToViewModel(
|
||||
) {
|
||||
when (val feedListUIState = inReplyToFeedListUIState) {
|
||||
press.mantra.compose.ui.view.state.FeedListUIState.Error -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.something_went_wrong),
|
||||
)
|
||||
}
|
||||
ErrorState()
|
||||
}
|
||||
is press.mantra.compose.ui.view.state.FeedListUIState.Loaded -> {
|
||||
if (feedListUIState.localNostrEvents.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(MaterialTheme.spacing.space600)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.no_events_were_found),
|
||||
)
|
||||
}
|
||||
EmptyState(message = stringResource(Res.string.no_replies_to_this_yet))
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
+12
-2
@@ -40,19 +40,29 @@ class KeyPackageManagementViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun publishNewKeyPackage() {
|
||||
/**
|
||||
* @param onDone called on completion so the screen can report it. Both of these were
|
||||
* fire and forget: you tapped, a coroutine ran, and nothing on screen changed --
|
||||
* which is indistinguishable from a tap that missed.
|
||||
*/
|
||||
fun publishNewKeyPackage(onDone: () -> Unit = {}) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// TODO: Rotate all other keys...
|
||||
marmotRepository.publishMarmotKeyPackageBundle(
|
||||
publicKey = activeUserPublicKey,
|
||||
nsecPassword = "" // TODO: Implement nsecPassword logic...
|
||||
)
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
|
||||
fun rotateKeyPackage(marmotKeyPackageBundle: MarmotKeyPackageBundle) {
|
||||
fun rotateKeyPackage(
|
||||
marmotKeyPackageBundle: MarmotKeyPackageBundle,
|
||||
onDone: () -> Unit = {},
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
marmotRepository.rotateMarmotKeyPackageBundle(marmotKeyPackageBundle)
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user