diff --git a/CLAUDE.md b/CLAUDE.md index 3c68fa57..de57265a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -132,6 +132,28 @@ ScreenStateTransition(viewModel.uiState) { uiState -> It only works where the `when` is the composable's whole body — `AnimatedContent` is a layout node, so wrapping one inside a `Column` takes its branches out of `ColumnScope`. +## A pushed screen has a way back + +```kotlin +TopAppBar( + title = { Text(stringResource(Res.string.add_post)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, // yes +) +TopAppBar(title = { … }) // no, on a pushed screen +``` + +A screen reached by `navController.navigate(...)` takes an `onNavigateBack: () -> Unit` +and puts `NavigateBackButton` in its app bar's leading slot; the host passes +`popBackStack()`. Android has a system back and iOS an edge swipe, but the desktop target +has neither, and a pushed screen with no button there is a dead end. Twenty-two were. + +The button is *only* for pushed screens. The three top-level destinations have the +navigation bar instead, and a screen that cleared the stack to get there -- onboarding, +the loading gate -- has nothing under it to pop to. Where one composable is reached both +ways, the callback is nullable and the caller decides: `ChatRoomMessagingScreen` is a +destination on a phone and the home screen's detail pane in a wide window, and in the +pane the list beside it is the way back. + ## Layout adapts to the window, not to the composable ```kotlin diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 3651aa15..f74dd05c 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -679,4 +679,6 @@ You have not backed up your nostr secret key These are your keys. Keep them safe so they can keep unlocking this profile, even when you lose or change your phone. You said you stored it + + New note diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AcceptCuratedSuggestionScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AcceptCuratedSuggestionScreen.kt index 9b21092f..dd0d5544 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AcceptCuratedSuggestionScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AcceptCuratedSuggestionScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Delete @@ -111,6 +110,7 @@ import press.mantra.compose.ui.composable.widgets.Decorative import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.rememberNotifier import press.mantra.compose.ui.theme.ConformancePreviews @@ -221,11 +221,7 @@ fun AcceptCuratedSuggestionScreen( topBar = { TopAppBar( title = { state.localChatRoom.RenderChatRoomTitleText() }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) }, bottomBar = { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt index ecbb13ae..6f4b486f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Bento import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Hub @@ -23,7 +22,6 @@ import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -72,6 +70,7 @@ import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.ButtonDefaults import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @@ -109,18 +108,7 @@ fun ActiveProfileScreen( text = stringResource(Res.string.profile) ) }, - navigationIcon = { - IconButton( - onClick = { - onNavigateBack.invoke() - } - ) { - Icon( - Icons.Default.ArrowBack, - contentDescription = "Back" - ) - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt index 693afa49..60024cbb 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt @@ -84,6 +84,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -101,6 +102,7 @@ fun AddArtifactScreen( frostSigningRepository: FrostSigningRepository, onNavigateToRouteAndPopUpInclusive: (Route) -> Unit, onNavigateToRoute: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val addArtifactViewModel: AddArtifactViewModel = viewModel( factory = AddArtifactViewModel.factory( @@ -160,9 +162,7 @@ fun AddArtifactScreen( title = { addArtifactUIState.localChatRoom.RenderChatRoomTitleText() }, - actions = { - - } + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -430,7 +430,8 @@ private fun AddArtifactScreenPreview() { mantraRepository = MantraRepository.NO_OP_MANTRA_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, onNavigateToRouteAndPopUpInclusive = {}, - onNavigateToRoute = {} + onNavigateToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt index f46431ef..4fa54ce0 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.DriveFileRenameOutline import androidx.compose.material3.BottomAppBar @@ -19,7 +18,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -67,6 +65,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3Api::class) @@ -146,14 +145,7 @@ fun AddChapterScreen( topBar = { TopAppBar( title = { Text(stringResource(Res.string.add_chapter_to, addChapterUIState.artifact.name)) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt index f352dd58..ebd8d934 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt @@ -68,6 +68,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -84,6 +85,7 @@ fun AddDialectScreen( frostSigningRepository: FrostSigningRepository, onNavigateToRouteAndPopUpInclusive: (Route) -> Unit, onNavigateToRoute: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val addDialectViewModel: AddDialectViewModel = viewModel( factory = AddDialectViewModel.factory( @@ -131,9 +133,7 @@ fun AddDialectScreen( title = { addDialectUIState.localChatRoom.RenderChatRoomTitleText() }, - actions = { - - } + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -364,7 +364,8 @@ private fun AddDialectScreenPreview() { chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, onNavigateToRouteAndPopUpInclusive = {}, - onNavigateToRoute = {} + onNavigateToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddGroupPostScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddGroupPostScreen.kt index ac0bdc77..208b9346 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddGroupPostScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddGroupPostScreen.kt @@ -52,6 +52,7 @@ import press.mantra.compose.ui.composable.navigation.routes.Route import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.profile.ProfileAvatar import press.mantra.compose.ui.composable.widgets.rememberNotifier @@ -96,6 +97,7 @@ fun AddGroupPostScreen( chatRepository: ChatRepository, frostSigningRepository: FrostSigningRepository, onNavigateToRouteAndPopUpInclusive: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val addGroupPostViewModel: AddGroupPostViewModel = viewModel( factory = AddGroupPostViewModel.factory( @@ -138,7 +140,8 @@ fun AddGroupPostScreen( modifier = Modifier.imePadding(), topBar = { TopAppBar( - title = { Text(text = stringResource(Res.string.add_post)) } + title = { Text(text = stringResource(Res.string.add_post)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -326,7 +329,8 @@ private fun AddGroupPostScreenPreview() { ), chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, - onNavigateToRouteAndPopUpInclusive = {} + onNavigateToRouteAndPopUpInclusive = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt index d8c93b38..21f5dffc 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment @@ -43,10 +44,12 @@ import mantra.composeapp.generated.resources.direct_message_detail import mantra.composeapp.generated.resources.direct_message_functionality_will_be_here import mantra.composeapp.generated.resources.invite import mantra.composeapp.generated.resources.invite_2 +import mantra.composeapp.generated.resources.invite_new_member 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -62,6 +65,7 @@ fun AddMemberToChatRoomConfirmationScreen( nostrRepository: NostrRepository, chatRepository: ChatRepository, onInviteSent: () -> Unit, + onNavigateBack: () -> Unit, ) { val addMemberToChatRoomConfirmationViewModel: AddMemberToChatRoomConfirmationViewModel = viewModel( factory = AddMemberToChatRoomConfirmationViewModel.factory( @@ -94,22 +98,14 @@ fun AddMemberToChatRoomConfirmationScreen( is AddMemberToChatRoomConfirmationUIState.Loaded -> { Scaffold( snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, - // topBar = { - // TopAppBar( - // title = { - // val title = if (addMemberToChatRoomConfirmationUIState.localChatRoom.chatRoom.subject != null) { - // "Invite new member to ${addMemberToChatRoomConfirmationUIState.localChatRoom.chatRoom.subject}" - // } else { - // "Invite new member to chat" - // } - // Text( - // text = title, - // ) - // }, - // actions = { - // } - // ) - // } + topBar = { + // The body already names who is being invited to which room, + // so the bar says only what the screen is for. + TopAppBar( + title = { Text(stringResource(Res.string.invite_new_member)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, + ) + }, bottomBar = { BottomAppBar( floatingActionButton = { @@ -252,7 +248,8 @@ private fun ChatRoomDetailScreenPreview() { ), nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY, chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, - onInviteSent = {} + onInviteSent = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt index 7f46159b..2dfc9a4a 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Translate import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ButtonDefaults @@ -19,7 +18,6 @@ import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FilterChip import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -68,6 +66,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @@ -145,14 +144,7 @@ fun AddTranslationArtifactVersionScreen( topBar = { TopAppBar( title = { Text(stringResource(Res.string.translate, addTranslationUIState.artifact.name)) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt index 442aba14..b8fd9da9 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Article import androidx.compose.material.icons.filled.PostAdd import androidx.compose.material.icons.filled.Translate @@ -19,7 +18,6 @@ import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -67,6 +65,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3Api::class) @@ -111,14 +110,7 @@ fun ArtifactDetailScreen( topBar = { TopAppBar( title = { Text(artifact.name) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/BroadcastGroupSignedEventScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/BroadcastGroupSignedEventScreen.kt index 17733468..cfc9e191 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/BroadcastGroupSignedEventScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/BroadcastGroupSignedEventScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.CellTower import androidx.compose.material.icons.filled.ContentCopy @@ -98,6 +97,7 @@ import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.rememberNotifier +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews import press.mantra.compose.ui.theme.MantraTheme import press.mantra.compose.ui.theme.readableContent @@ -216,11 +216,7 @@ fun BroadcastGroupSignedEventScreen( title = { broadcastGroupSignedEventUIState.localChatRoom.RenderChatRoomTitleText() }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) }, bottomBar = { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt index 37ae0687..fd5feb3b 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt @@ -10,13 +10,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Segment import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -51,6 +49,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3Api::class) @@ -94,14 +93,7 @@ fun ChapterDetailScreen( topBar = { TopAppBar( title = { Text(chapter.name) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt index 008dfb7c..2027e905 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt @@ -12,12 +12,14 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Badge import androidx.compose.material.icons.filled.Description import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,19 +35,23 @@ import org.jetbrains.compose.resources.stringResource import mantra.composeapp.generated.resources.choose_who_to_chat_with import mantra.composeapp.generated.resources.enter_the_name_you_want_to_use_for_your import mantra.composeapp.generated.resources.name_eg_group_discussions +import mantra.composeapp.generated.resources.new_chat import mantra.composeapp.generated.resources.this_will_be_shown_when_people_open_the_chat 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.ConformancePreviews +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ChatRoomCreationScreen( activeUserPublicKey: HexKey, - onNavigateToRoute: (Route) -> Unit + onNavigateToRoute: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val chatRoomCreationViewModel: ChatRoomCreationViewModel = viewModel ( factory = ChatRoomCreationViewModel.factory() @@ -55,7 +61,16 @@ fun ChatRoomCreationScreen( // keyboard covers whatever is being typed into. Scaffold( snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, - modifier = Modifier.imePadding()) { innerPadding -> + modifier = Modifier.imePadding(), + topBar = { + // Named after the button that opens it, which is the first of three steps; + // the next two name themselves after the chat this one is creating. + TopAppBar( + title = { Text(stringResource(Res.string.new_chat)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, + ) + }, + ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding).readableContent().fillMaxWidth() ) { @@ -181,7 +196,8 @@ private fun LoadingScreenPreview() { ) { ChatRoomCreationScreen( activeUserPublicKey = "", - onNavigateToRoute = {} + onNavigateToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt index 2540e326..b2148a64 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt @@ -192,6 +192,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -208,7 +209,8 @@ fun ChatRoomDetailScreen( chatRepository: ChatRepository, mantraRepository: MantraRepository, onNavigateToRoute: (Route) -> Unit, - onPopBackToRoute: (Route) -> Unit + onPopBackToRoute: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val chatRoomDetailViewModel: ChatRoomDetailViewModel = viewModel( factory = ChatRoomDetailViewModel.factory( @@ -245,6 +247,7 @@ fun ChatRoomDetailScreen( title = { chatRoomDetailUIState.localChatRoom.RenderChatRoomTitleText() }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) } ) { innerPadding -> @@ -2213,7 +2216,8 @@ private fun ChatRoomMessagingScreenPreview() { chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, mantraRepository = MantraRepository.NO_OP_MANTRA_REPOSITORY, onNavigateToRoute = {}, - onPopBackToRoute = {} + onPopBackToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt index e2a70c8f..e678a623 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt @@ -69,6 +69,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -86,6 +87,13 @@ fun ChatRoomMessagingScreen( dkgRepository: DkgRepository, onNavigateToRouteAndPopUpInclusive: (Route) -> Unit, onNavigateToRoute: (Route) -> Unit, + /** + * `null` when this is the home screen's detail pane rather than a pushed route. In + * the pane the room list is beside the transcript and there is nothing to pop, so + * the bar draws no back button -- M3's list-detail has the list do that job. On a + * phone the same screen is a destination, and the button is what leaves it. + */ + onNavigateBack: (() -> Unit)?, ) { val chatRoomMessagingViewModel: ChatRoomMessagingViewModel = viewModel( factory = ChatRoomMessagingViewModel.factory( @@ -133,6 +141,9 @@ fun ChatRoomMessagingScreen( title = { chatRoomDetailUIState.localChatRoom.RenderChatRoomTitleText() }, + navigationIcon = { + if (onNavigateBack != null) NavigateBackButton(onNavigateBack) + }, actions = { IconButton( onClick = { @@ -491,7 +502,8 @@ private fun ChatRoomMessagingScreenPreview() { frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, dkgRepository = DkgRepository.NO_OP_DKG_REPOSITORY, onNavigateToRouteAndPopUpInclusive = {}, - onNavigateToRoute = {} + onNavigateToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt index da11d54a..28cc9ee3 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.material.icons.filled.Badge import androidx.compose.material.icons.filled.Description import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -23,6 +24,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -60,14 +62,16 @@ import mantra.composeapp.generated.resources.you_are_about_to_create_a_nostr_pro 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.ConformancePreviews -@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable fun CreateProfileScreen( initialCreateProfileUIState: CreateProfileUIState = CreateProfileUIState.Declaration, onNavigateToEndThis: () -> Unit, + onNavigateBack: () -> Unit, nostrRepository: press.mantra.compose.repository.NostrRepository, marmotRepository: MarmotRepository, writeSeed: (mnemonics: List, onSeedWritten: () -> Unit, onSeedWriteError: () -> Unit) -> Unit @@ -83,7 +87,18 @@ fun CreateProfileScreen( // keyboard covers whatever is being typed into. Scaffold( snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, - modifier = Modifier.imePadding()) { innerPadding -> + modifier = Modifier.imePadding(), + topBar = { + // The name is in the bar, as it is on the sign-in screen beside this one on + // the landing page, and the steps below no longer repeat it as a headline. + // Back pops to the landing page; nothing is written until the last step + // confirms, so there is nothing to lose by leaving. + TopAppBar( + title = { Text(stringResource(Res.string.create_profile)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, + ) + }, + ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) .readableContent() @@ -99,12 +114,6 @@ fun CreateProfileScreen( when (val createAccountUIState = createProfileViewModel.createProfileUIState.value) { is CreateProfileUIState.Declaration -> { - - Text( - stringResource(Res.string.create_profile), - style = MaterialTheme.typography.headlineSmall - ) - Text( stringResource(Res.string.you_are_about_to_create_a_nostr_profile), style = MaterialTheme.typography.bodyMedium, @@ -133,11 +142,6 @@ fun CreateProfileScreen( } } is CreateProfileUIState.InputPrompt -> { - Text( - stringResource(Res.string.create_profile), - style = MaterialTheme.typography.headlineSmall - ) - TextField( modifier = Modifier.fillMaxWidth(), state = createProfileViewModel.createProfileFormState.nameField.textFieldState, @@ -438,6 +442,7 @@ fun CreateAccountScreenPreview() { // ) // ), onNavigateToEndThis = {}, + onNavigateBack = {}, writeSeed = { _, _, _ -> }, nostrRepository = press.mantra.compose.repository.NostrRepository.NO_OP_NOSTR_REPOSITORY, marmotRepository = MarmotRepository.NO_OP_MARMOT_KEY_PACKAGE_BUNDLE diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CuratedSuggestionListScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CuratedSuggestionListScreen.kt index 06de3aa2..8f7b69c6 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CuratedSuggestionListScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CuratedSuggestionListScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Draw import androidx.compose.material3.Button @@ -20,7 +19,6 @@ import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -85,6 +83,7 @@ import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.dialogs.ModalBottomSheet import press.mantra.compose.ui.composable.widgets.profile.ProfileAvatar +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews import press.mantra.compose.ui.theme.MantraTheme import press.mantra.compose.ui.theme.readableContent @@ -153,11 +152,7 @@ fun CuratedSuggestionListScreen( overflow = TextOverflow.Ellipsis ) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { padding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt index b3766198..cc358aa5 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt @@ -45,6 +45,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent /** @@ -92,7 +93,10 @@ internal fun DkgApprovalScaffold( snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, topBar = { TopAppBar( - title = { Text(text = title, maxLines = 1, overflow = TextOverflow.Ellipsis) } + title = { Text(text = title, maxLines = 1, overflow = TextOverflow.Ellipsis) }, + // The same leave as "Not now" below, reachable from the loading and error + // states too -- which have no other way off the screen on desktop. + navigationIcon = { NavigateBackButton(onDone) }, ) } ) { padding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt index 342b6702..64ce688d 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt @@ -114,6 +114,7 @@ import mantra.composeapp.generated.resources.of_members_will_be_needed_to_sign_w import mantra.composeapp.generated.resources.shared_key_for import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -139,6 +140,7 @@ fun DkgRitualScreen( chatRepository: ChatRepository, dkgRepository: DkgRepository, onNavigateToRoute: (Route) -> Unit = {}, + onNavigateBack: () -> Unit, ) { val dkgRitualViewModel: DkgRitualViewModel = viewModel( factory = DkgRitualViewModel.factory( @@ -179,7 +181,8 @@ fun DkgRitualScreen( maxLines = 1, overflow = TextOverflow.Ellipsis ) - } + }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -884,7 +887,8 @@ private fun DkgRitualScreenPreview() { ), activeIdentityStateFlow = MutableStateFlow(null), chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, - dkgRepository = DkgRepository.NO_OP_DKG_REPOSITORY + dkgRepository = DkgRepository.NO_OP_DKG_REPOSITORY, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupCuratedSchemaScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupCuratedSchemaScreen.kt index be868d9b..6a113f52 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupCuratedSchemaScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupCuratedSchemaScreen.kt @@ -104,6 +104,7 @@ import press.mantra.compose.ui.composable.widgets.Decorative import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.dialogs.ModalBottomSheet import press.mantra.compose.ui.composable.widgets.rememberNotifier @@ -268,6 +269,7 @@ fun EditGroupCuratedSchemaScreen( chatRepository: ChatRepository, frostSigningRepository: FrostSigningRepository, onNavigateToRouteAndPopUpInclusive: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val editGroupCuratedSchemaViewModel: EditGroupCuratedSchemaViewModel = viewModel( factory = EditGroupCuratedSchemaViewModel.factory( @@ -344,7 +346,8 @@ fun EditGroupCuratedSchemaScreen( TopAppBar( title = { editGroupCuratedSchemaUIState.localChatRoom.RenderChatRoomTitleText() - } + }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -1514,7 +1517,8 @@ private fun EditGroupCuratedSchemaScreenPreview() { ), chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, - onNavigateToRouteAndPopUpInclusive = {} + onNavigateToRouteAndPopUpInclusive = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupNostrProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupNostrProfileScreen.kt index 33e8d287..6914b722 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupNostrProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupNostrProfileScreen.kt @@ -59,6 +59,7 @@ import press.mantra.compose.ui.composable.navigation.routes.Route import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.rememberNotifier import press.mantra.compose.ui.theme.ConformancePreviews @@ -113,6 +114,7 @@ fun EditGroupNostrProfileScreen( chatRepository: ChatRepository, frostSigningRepository: FrostSigningRepository, onNavigateToRouteAndPopUpInclusive: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val editGroupNostrProfileViewModel: EditGroupNostrProfileViewModel = viewModel( factory = EditGroupNostrProfileViewModel.factory( @@ -170,7 +172,8 @@ fun EditGroupNostrProfileScreen( title = { editGroupNostrProfileUIState.localChatRoom .RenderChatRoomTitleText() - } + }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -416,7 +419,8 @@ private fun EditGroupNostrProfileScreenPreview() { ), chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, - onNavigateToRouteAndPopUpInclusive = {} + onNavigateToRouteAndPopUpInclusive = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupRelaysScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupRelaysScreen.kt index 6a0003f3..7b076c84 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupRelaysScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/EditGroupRelaysScreen.kt @@ -68,6 +68,7 @@ import press.mantra.compose.ui.composable.widgets.EmptyState import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.rememberNotifier import press.mantra.compose.ui.theme.ConformancePreviews @@ -133,6 +134,7 @@ fun EditGroupRelaysScreen( chatRepository: ChatRepository, frostSigningRepository: FrostSigningRepository, onNavigateToRouteAndPopUpInclusive: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val editGroupRelaysViewModel: EditGroupRelaysViewModel = viewModel( factory = EditGroupRelaysViewModel.factory( @@ -192,7 +194,8 @@ fun EditGroupRelaysScreen( TopAppBar( title = { editGroupRelaysUIState.localChatRoom.RenderChatRoomTitleText() - } + }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -570,7 +573,8 @@ private fun EditGroupRelaysScreenPreview() { ), chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, - onNavigateToRouteAndPopUpInclusive = {} + onNavigateToRouteAndPopUpInclusive = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt index f9917824..fbecea82 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Draw import androidx.compose.material.icons.filled.ErrorOutline @@ -25,7 +24,6 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -70,6 +68,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.ConformancePreviews @@ -125,11 +124,7 @@ fun FrostSigningScreen( overflow = TextOverflow.Ellipsis ) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { padding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt index fc20e8ec..2b91c937 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt @@ -302,6 +302,8 @@ fun HomeScreen( // created and has an id at last. onNavigateToRouteAndPopUpInclusive = onOpenChatRoom, onNavigateToRoute = onNavigateToRoute, + // The list is beside it; there is nothing to pop. + onNavigateBack = null, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ImplementationPendingScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ImplementationPendingScreen.kt index cd4c2015..98216fc5 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ImplementationPendingScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ImplementationPendingScreen.kt @@ -4,10 +4,12 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -18,15 +20,35 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.ConformancePreviews +/** + * @param onNavigateBack `null` when this screen is the root of the stack, which it is + * when the navigation machine lands on an error and clears everything under it. Pushed + * from a "learn more" or an "edit profile", there is a screen to return to and this is + * how. Only the host knows which, so it decides. + */ +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ImplementationPendingScreen( - text: String + text: String, + onNavigateBack: (() -> Unit)?, ) { - Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> + Scaffold( + snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, + topBar = { + TopAppBar( + // The name of the thing that is not built yet, which is what was tapped. + title = { Text(text) }, + navigationIcon = { + if (onNavigateBack != null) NavigateBackButton(onNavigateBack) + }, + ) + }, + ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { @@ -52,7 +74,8 @@ private fun ImplementationPendingScreenPreview() { modifier = Modifier.padding(MaterialTheme.spacing.space250) ) { ImplementationPendingScreen( - "Sign in" + text = "Sign in", + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt index e787472d..b45367e9 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.DoneAll import androidx.compose.material.icons.filled.LockReset import androidx.compose.material.icons.filled.Unpublished @@ -59,6 +58,7 @@ import mantra.composeapp.generated.resources.key_package_published import mantra.composeapp.generated.resources.key_package_rotated import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @@ -102,18 +102,7 @@ fun KeyPackageManagementScreen( text = stringResource(Res.string.key_package_management) ) }, - navigationIcon = { - IconButton( - onClick = { - onNavigateBack.invoke() - } - ) { - Icon( - Icons.Default.ArrowBack, - contentDescription = "Back" - ) - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyRecoveryScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyRecoveryScreen.kt index b7e12aa3..b70a798c 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyRecoveryScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyRecoveryScreen.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AddToDrive -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.LocalHospital import androidx.compose.material.icons.filled.Key @@ -25,7 +24,6 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -71,6 +69,7 @@ import press.mantra.compose.ui.composable.navigation.routes.NostrSecretRoute import press.mantra.compose.ui.composable.navigation.routes.RecoveryPhraseRoute import press.mantra.compose.ui.composable.navigation.routes.Route import press.mantra.compose.ui.composable.widgets.Decorative +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.spacing @@ -109,18 +108,7 @@ fun KeyRecoveryScreen( text = stringResource(Res.string.key_recovery) ) }, - navigationIcon = { - IconButton( - onClick = { - onNavigateBack.invoke() - } - ) { - Icon( - Icons.Default.ArrowBack, - contentDescription = "Back" - ) - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt index 348a912a..f141fb24 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment @@ -34,12 +35,14 @@ 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 +import mantra.composeapp.generated.resources.post 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -95,20 +98,41 @@ fun NostrEventDetailScreen( nostrRepository = nostrRepository ) } + // The two kinds with a detail of their own draw their bar inside it. The + // rest are a placeholder, and a placeholder pushed onto the stack still + // needs the way off it. RepostEvent.KIND -> { // TODO: RePost... - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text(feedListUIState.localNostrEvent.nostrEvent.content) + Scaffold( + snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, + topBar = { + TopAppBar( + title = { Text(stringResource(Res.string.post)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, + ) + }, + ) { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(feedListUIState.localNostrEvent.nostrEvent.content) - Text(stringResource(Res.string.post_functionality_coming_soon)) + Text(stringResource(Res.string.post_functionality_coming_soon)) + } } } else -> { - Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> + Scaffold( + snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, + topBar = { + TopAppBar( + title = { Text(stringResource(Res.string.post)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, + ) + }, + ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrSecretScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrSecretScreen.kt index 12d49d6c..bf04707a 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrSecretScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrSecretScreen.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.DeleteForever import androidx.compose.material.icons.filled.Visibility @@ -27,7 +26,6 @@ import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost @@ -48,7 +46,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign import androidx.lifecycle.viewmodel.compose.viewModel import mantra.composeapp.generated.resources.Res -import mantra.composeapp.generated.resources.back import mantra.composeapp.generated.resources.backup_confirmation import mantra.composeapp.generated.resources.cancel import mantra.composeapp.generated.resources.copied_the_nsec @@ -86,6 +83,7 @@ import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.composable.widgets.rememberNotifier +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.spacing @@ -201,14 +199,7 @@ private fun NostrSecretContent( topBar = { TopAppBar( title = { Text(text = stringResource(Res.string.nostr_secret_key)) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back) - ) - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt index 2ac182c3..a330a4c3 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material.icons.filled.Draw @@ -22,7 +21,6 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme @@ -64,6 +62,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.ConformancePreviews @@ -119,11 +118,7 @@ fun ProposalListScreen( overflow = TextOverflow.Ellipsis ) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { padding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposeGroupEventScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposeGroupEventScreen.kt index b82b529e..6b4f4a86 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposeGroupEventScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposeGroupEventScreen.kt @@ -58,6 +58,7 @@ import press.mantra.compose.ui.composable.navigation.routes.Route import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.profile.ProfileAvatar import press.mantra.compose.ui.composable.widgets.rememberNotifier @@ -115,6 +116,7 @@ fun ProposeGroupEventScreen( chatRepository: ChatRepository, frostSigningRepository: FrostSigningRepository, onNavigateToRouteAndPopUpInclusive: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val proposeGroupEventViewModel: ProposeGroupEventViewModel = viewModel( factory = ProposeGroupEventViewModel.factory( @@ -164,7 +166,8 @@ fun ProposeGroupEventScreen( modifier = Modifier.imePadding(), topBar = { TopAppBar( - title = { Text(text = stringResource(Res.string.propose_event)) } + title = { Text(text = stringResource(Res.string.propose_event)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -458,7 +461,8 @@ private fun ProposeGroupEventScreenPreview() { ), chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, - onNavigateToRouteAndPopUpInclusive = {} + onNavigateToRouteAndPopUpInclusive = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/RecoveryPhraseScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/RecoveryPhraseScreen.kt index 415d9973..543a75ca 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/RecoveryPhraseScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/RecoveryPhraseScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material.icons.filled.Warning @@ -23,7 +22,6 @@ import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -66,6 +64,7 @@ import org.jetbrains.compose.resources.stringResource import press.mantra.compose.ui.composable.widgets.Decorative import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.spacing @@ -136,18 +135,7 @@ private fun RecoveryPhraseContent( text = stringResource(Res.string.recovery_phrase) ) }, - navigationIcon = { - IconButton( - onClick = { - onNavigateBack.invoke() - } - ) { - Icon( - Icons.Default.ArrowBack, - contentDescription = "Back" - ) - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt index 67b25221..ea39c367 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt @@ -47,6 +47,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -61,6 +62,7 @@ fun SearchMemberToAddToChatRoomScreen( nostrRepository: NostrRepository, chatRepository: ChatRepository, onNavigateToRoute: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val searchMemberToAddToChatRoomViewModel: SearchMemberToAddToChatRoomViewModel = viewModel( factory = SearchMemberToAddToChatRoomViewModel.factory( @@ -102,7 +104,8 @@ fun SearchMemberToAddToChatRoomScreen( Text( text = title, ) - } + }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) } ) { innerPadding -> @@ -256,7 +259,8 @@ private fun ChatRoomDetailScreenPreview() { ), nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY, chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, - onNavigateToRoute = {} + onNavigateToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt index f1aa9dbc..772a2ce9 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt @@ -54,6 +54,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -74,6 +75,7 @@ fun SelectChatRoomMembersScreen( initialSelectChatRoomMembersUIState: SelectChatRoomMembersUIState = SelectChatRoomMembersUIState.Loading, nostrRepository: NostrRepository, onNavigateToRoute: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val selectChatRoomMembersViewModel: SelectChatRoomMembersViewModel = viewModel( factory = SelectChatRoomMembersViewModel.factory( @@ -114,7 +116,8 @@ fun SelectChatRoomMembersScreen( maxLines = 1, overflow = TextOverflow.Ellipsis ) - } + }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -302,7 +305,8 @@ private fun SelectChatRoomMembersScreenPreview() { ) ), nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY, - onNavigateToRoute = {} + onNavigateToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt index 0d62827e..5f5399fd 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt @@ -71,6 +71,7 @@ import mantra.composeapp.generated.resources.was_created_but_its_shared_key_cere import mantra.composeapp.generated.resources.you_and_others import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.theme.ConformancePreviews @@ -92,6 +93,7 @@ fun SelectChatRoomTypeScreen( chatRepository: ChatRepository, dkgRepository: DkgRepository, onNavigateToRoute: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val selectChatRoomTypeViewModel: SelectChatRoomTypeViewModel = viewModel( factory = SelectChatRoomTypeViewModel.factory( @@ -142,7 +144,8 @@ fun SelectChatRoomTypeScreen( maxLines = 1, overflow = TextOverflow.Ellipsis ) - } + }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { @@ -494,7 +497,8 @@ private fun SelectChatRoomTypeScreenPreview() { nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY, chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, dkgRepository = DkgRepository.NO_OP_DKG_REPOSITORY, - onNavigateToRoute = {} + onNavigateToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectSubgroupAdminsScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectSubgroupAdminsScreen.kt index 94b311b8..44110cbf 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectSubgroupAdminsScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectSubgroupAdminsScreen.kt @@ -59,6 +59,7 @@ import press.mantra.compose.ui.composable.widgets.Decorative import press.mantra.compose.ui.composable.widgets.ErrorState import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.profile.ProfileAvatar import press.mantra.compose.ui.theme.ConformancePreviews @@ -84,7 +85,8 @@ fun SelectSubgroupAdminsScreen( activeIdentityStateFlow: StateFlow, chatRepository: ChatRepository, dkgRepository: DkgRepository, - onNavigateToRoute: (Route) -> Unit + onNavigateToRoute: (Route) -> Unit, + onNavigateBack: () -> Unit, ) { val viewModel: SelectSubgroupAdminsViewModel = viewModel( factory = SelectSubgroupAdminsViewModel.factory( @@ -115,7 +117,10 @@ fun SelectSubgroupAdminsScreen( is SelectSubgroupAdminsUIState.Loaded -> Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { - TopAppBar(title = { Text(stringResource(Res.string.new_subgroup)) }) + TopAppBar( + title = { Text(stringResource(Res.string.new_subgroup)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, + ) }, floatingActionButton = { ExtendedFloatingActionButton( @@ -334,7 +339,8 @@ private fun SelectSubgroupAdminsScreenPreview() { activeIdentityStateFlow = kotlinx.coroutines.flow.MutableStateFlow(null), chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, dkgRepository = DkgRepository.NO_OP_DKG_REPOSITORY, - onNavigateToRoute = {} + onNavigateToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt index b458efb5..fc53f059 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt @@ -10,13 +10,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -55,6 +53,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @@ -92,18 +91,7 @@ fun ShareProfileScreen( text = stringResource(Res.string.profile) ) }, - navigationIcon = { - IconButton( - onClick = { - onNavigateBack.invoke() - } - ) { - Icon( - Icons.Default.ArrowBack, - contentDescription = "Back" - ) - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt index f60352fd..8dac4889 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ContentPaste import androidx.compose.material.icons.filled.Key import androidx.compose.material.icons.filled.Spellcheck @@ -40,7 +39,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel import mantra.composeapp.generated.resources.Res import mantra.composeapp.generated.resources.a_password_protected_key_ncryptsec_is_not import mantra.composeapp.generated.resources.an_npub_is_a_public_key_mantra_needs_the -import mantra.composeapp.generated.resources.back import mantra.composeapp.generated.resources.enter_a_recovery_phrase_or_an_nsec_to_sign_in import mantra.composeapp.generated.resources.next import mantra.composeapp.generated.resources.paste @@ -73,6 +71,7 @@ import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.composable.widgets.ScreenStateTransition import press.mantra.compose.ui.composable.widgets.rememberNotifier +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.spacing @@ -121,14 +120,7 @@ fun SignInToProfileScreen( topBar = { TopAppBar( title = { Text(stringResource(Res.string.sign_in)) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back) - ) - } - } + navigationIcon = { NavigateBackButton(onNavigateBack) } ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SovereignWalletStartupScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SovereignWalletStartupScreen.kt index b2a030e1..9bd960f7 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SovereignWalletStartupScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SovereignWalletStartupScreen.kt @@ -70,7 +70,9 @@ fun SovereignWalletStartupScreen( } is ListWalletState.Error -> { ImplementationPendingScreen( - "Failed to load wallet data", + text = "Failed to load wallet data", + // Drawn inside the startup gate, which is the root of the stack. + onNavigateBack = null, ) } is ListWalletState.Success -> { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt index 0d767b3d..bd9bd0af 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material3.BottomAppBar import androidx.compose.material3.ButtonDefaults @@ -21,7 +20,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -65,6 +63,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3Api::class) @@ -124,14 +123,7 @@ fun TranslateChunkScreen( topBar = { TopAppBar( title = { Text(stringResource(Res.string.translate_chunk)) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) }, bottomBar = { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt index fef9c9d2..732a9f32 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt @@ -10,14 +10,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Article import androidx.compose.material.icons.filled.PlaylistAdd import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -60,6 +58,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3Api::class) @@ -108,14 +107,7 @@ fun TranslationArtifactVersionDetailScreen( topBar = { TopAppBar( title = { Text(translation.name) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt index 1d0b5df7..c4805f6f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt @@ -15,12 +15,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -55,6 +53,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.composable.widgets.ScreenStateTransition +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.ConformancePreviews @OptIn(ExperimentalMaterial3Api::class) @@ -98,14 +97,7 @@ fun TranslationChapterScreen( topBar = { TopAppBar( title = { Text(stringResource(Res.string.chapter_translation)) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, ) } ) { innerPadding -> diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt index bdef27a3..4efd8f31 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon @@ -29,6 +30,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.key @@ -46,6 +48,7 @@ import kotlin.time.Clock import press.mantra.compose.ui.theme.spacing import mantra.composeapp.generated.resources.Res import org.jetbrains.compose.resources.stringResource +import mantra.composeapp.generated.resources.new_note import mantra.composeapp.generated.resources.next import mantra.composeapp.generated.resources.something_went_wrong_and_we_were_unable_to_3 import mantra.composeapp.generated.resources.the_above_will_be_your_new_note @@ -57,10 +60,11 @@ import mantra.composeapp.generated.resources.what_vibrations_do_you_want_to_send import mantra.composeapp.generated.resources.replying_to import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.ConformancePreviews -@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun WriteNewNoteScreen( activeUserPublicKey: HexKey, @@ -69,6 +73,7 @@ fun WriteNewNoteScreen( quotedNostrEventId: HexKey?, onNostrEventPublished: () -> Unit, onNavigateToNostrEvent: (HexKey) -> Unit, + onNavigateBack: () -> Unit, nostrRepository: press.mantra.compose.repository.NostrRepository ) { val writeNewNoteViewModel: press.mantra.compose.ui.view.model.WriteNewNoteViewModel = viewModel ( @@ -84,7 +89,17 @@ fun WriteNewNoteScreen( // keyboard covers whatever is being typed into. Scaffold( snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, - modifier = Modifier.imePadding()) { innerPadding -> + modifier = Modifier.imePadding(), + topBar = { + // "New note" whether it is a reply, a quote or neither: the body says which + // above the field. Back pops the route and drops the draft, which is what + // the platform's own back does on this screen already. + TopAppBar( + title = { Text(stringResource(Res.string.new_note)) }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, + ) + }, + ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) .readableContent() @@ -522,6 +537,7 @@ It has survived not only five centuries, but also the leap into electronic types ), onNostrEventPublished = {}, onNavigateToNostrEvent = {}, + onNavigateBack = {}, nostrRepository = press.mantra.compose.repository.NostrRepository.NO_OP_NOSTR_REPOSITORY ) } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/MantraNavHost.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/MantraNavHost.kt index 5cdb1334..9dbcedd5 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/MantraNavHost.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/MantraNavHost.kt @@ -500,6 +500,9 @@ fun MantraNavHost( popUpTo(0) } }, + onNavigateBack = { + navController.popBackStack() + }, nostrRepository = databaseNostrRepository, marmotRepository = databaseMarmotRepository, writeSeed = { words, onSeedWritten, onSeedWriteError -> @@ -532,6 +535,9 @@ fun MantraNavHost( navController.navigate( selectMembersRoute ) + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -547,6 +553,9 @@ fun MantraNavHost( navController.navigate( selectChatRoomTypeRoute ) + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -561,6 +570,9 @@ fun MantraNavHost( dkgRepository = databaseDkgRepository, onNavigateToRoute = { nextRoute -> navController.navigate(route = nextRoute) + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -576,6 +588,9 @@ fun MantraNavHost( dkgRepository = databaseDkgRepository, onNavigateToRoute = { approvalRoute -> navController.navigate(route = approvalRoute) + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -644,6 +659,9 @@ fun MantraNavHost( inclusive = true } } + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -666,6 +684,9 @@ fun MantraNavHost( ) ) }, + onNavigateBack = { + navController.popBackStack() + }, nostrRepository = databaseNostrRepository ) } @@ -918,6 +939,11 @@ fun MantraNavHost( navController.navigate( route = actionRoute ) + }, + // A pushed route, unlike the same screen inside the home screen's + // detail pane, so there is something to pop. + onNavigateBack = { + navController.popBackStack() } ) } @@ -941,6 +967,9 @@ fun MantraNavHost( route = popRoute, inclusive = false ) + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -958,6 +987,9 @@ fun MantraNavHost( route = chatRoomDetailRoute ) }, + onNavigateBack = { + navController.popBackStack() + }, ) } composable { backStackEntry -> @@ -980,6 +1012,11 @@ fun MantraNavHost( inclusive = true ) }, + // Back is to the search, not past it: a member who picked the wrong + // person wants the list again, not the room. + onNavigateBack = { + navController.popBackStack() + }, ) } composable { backStackEntry -> @@ -1008,6 +1045,9 @@ fun MantraNavHost( navController.navigate( route = actionRoute ) + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -1034,6 +1074,9 @@ fun MantraNavHost( navController.navigate( route = actionRoute ) + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -1054,6 +1097,9 @@ fun MantraNavHost( inclusive = true } } + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -1074,6 +1120,9 @@ fun MantraNavHost( inclusive = true } } + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -1094,6 +1143,9 @@ fun MantraNavHost( inclusive = true } } + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -1115,6 +1167,9 @@ fun MantraNavHost( inclusive = true } } + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -1198,6 +1253,9 @@ fun MantraNavHost( inclusive = true } } + }, + onNavigateBack = { + navController.popBackStack() } ) } @@ -1485,8 +1543,23 @@ fun MantraNavHost( } composable { backStackEntry -> val route: ImplementationPendingRoute = backStackEntry.toRoute() + + // Pushed from a "learn more" or an "edit profile", and also where the + // navigation observer above lands with `popUpTo(0)` on an error. Whether + // there is anything under this entry is what decides the back button, and + // it is remembered because the answer is read again while the screen is + // leaving, when the stack has already moved on. + val hasSomethingToReturnTo = remember { + navController.previousBackStackEntry != null + } + ImplementationPendingScreen( - route.name + text = route.name, + onNavigateBack = if (hasSomethingToReturnTo) { + { navController.popBackStack() } + } else { + null + } ) } } diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/NavigateBackButton.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/NavigateBackButton.kt new file mode 100644 index 00000000..4f43fcf2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/NavigateBackButton.kt @@ -0,0 +1,50 @@ +package press.mantra.compose.ui.composable.widgets + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import mantra.composeapp.generated.resources.Res +import mantra.composeapp.generated.resources.back +import org.jetbrains.compose.resources.stringResource + +/** + * The button in a pushed screen's leading slot, the one M3 calls the navigation icon. + * + * Before this file, 18 screens drew it and 22 did not, and the 18 spelled it four ways: + * `ArrowBack`, the auto-mirrored `ArrowBack`, `ArrowBackIosNew`, and a `"Back"` literal + * against a `stringResource`. A screen that is reached by pushing a route and has no way + * to pop it is a dead end wherever the platform gives no back gesture -- which is the + * desktop target, where a window has neither a back button nor an edge to swipe from. + * + * Two of the four spellings are decisions this component makes once: + * + * - **The icon is the auto-mirrored one.** "Back" points at the leading edge, and the + * leading edge is on the right in an RTL locale. `Icons.Filled.ArrowBack` is + * deprecated for exactly this reason, and the navigation transitions already mirror + * through `slideIntoContainer`; an arrow that did not would point the wrong way at the + * screen that was sliding the right way. + * - **The description is the catalogue's.** "Back" is what a screen reader says, and it + * is text, so it lives in `strings.xml` like every other word the app says. + * + * `IconButton` reserves the 48dp target itself, so there is no modifier to remember. + * + * Put it in `TopAppBar(navigationIcon = …)`, and only on a screen the user pushed onto + * the stack. A top-level destination has the navigation bar instead, and a screen that + * replaced the whole stack -- onboarding, the loading gate -- has nothing under it to + * return to; a back button there would pop to an empty host. + * + * @param onNavigateBack what popping means for the screen, which is nearly always the + * host's `popBackStack()`. Hoisted rather than read from a controller here so the + * screen stays previewable and testable without one. + */ +@Composable +fun NavigateBackButton(onNavigateBack: () -> Unit) { + IconButton(onClick = onNavigateBack) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(Res.string.back), + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt index 2ad86228..d8c0c463 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBackIosNew import androidx.compose.material.icons.filled.Block import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Error @@ -22,7 +21,6 @@ import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.MaterialTheme @@ -68,6 +66,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.ConformancePreviews @@ -101,16 +100,7 @@ fun MetadataEventDetail( modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { LargeTopAppBar( - navigationIcon = { - IconButton( - onClick = onNavigateBack - ) { - Icon( - Icons.Default.ArrowBackIosNew, - contentDescription = "Navigate back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, title = { Column( modifier = Modifier, diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt index 35304a9f..180c2475 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AddReaction -import androidx.compose.material.icons.filled.ArrowBackIosNew import androidx.compose.material.icons.filled.ChatBubble import androidx.compose.material.icons.filled.CurrencyBitcoin import androidx.compose.material.icons.filled.MoreVert @@ -57,6 +56,7 @@ 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 +import press.mantra.compose.ui.composable.widgets.NavigateBackButton import press.mantra.compose.ui.theme.readableContent import press.mantra.compose.ui.theme.ConformancePreviews @@ -87,16 +87,7 @@ fun TextNoteEventDetail( modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { TopAppBar( - navigationIcon = { - IconButton( - onClick = onNavigateBack - ) { - Icon( - Icons.Default.ArrowBackIosNew, - contentDescription = "Navigate back" - ) - } - }, + navigationIcon = { NavigateBackButton(onNavigateBack) }, title = { Text(stringResource(Res.string.post)) }, diff --git a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/EditGroupCuratedSchemaScreenJvmTest.kt b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/EditGroupCuratedSchemaScreenJvmTest.kt index 342753a0..5afdce93 100644 --- a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/EditGroupCuratedSchemaScreenJvmTest.kt +++ b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/EditGroupCuratedSchemaScreenJvmTest.kt @@ -226,7 +226,8 @@ class EditGroupCuratedSchemaScreenJvmTest { chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, - onNavigateToRouteAndPopUpInclusive = {} + onNavigateToRouteAndPopUpInclusive = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/GroupNostrProfileSectionJvmTest.kt b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/GroupNostrProfileSectionJvmTest.kt index 137aa291..79f58c1b 100644 --- a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/GroupNostrProfileSectionJvmTest.kt +++ b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/GroupNostrProfileSectionJvmTest.kt @@ -665,7 +665,8 @@ class GroupNostrProfileSectionJvmTest { chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, mantraRepository = MantraRepository.NO_OP_MANTRA_REPOSITORY, onNavigateToRoute = {}, - onPopBackToRoute = {} + onPopBackToRoute = {}, + onNavigateBack = {}, ) } } diff --git a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/NavigateBackButtonJvmTest.kt b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/NavigateBackButtonJvmTest.kt new file mode 100644 index 00000000..2ed6f184 --- /dev/null +++ b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/NavigateBackButtonJvmTest.kt @@ -0,0 +1,111 @@ +package press.mantra.compose.ui.composable + +import androidx.compose.runtime.Composable +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.runDesktopComposeUiTest +import press.mantra.compose.database.model.ChatRoom +import press.mantra.compose.database.model.intermdiate.LocalChatRoom +import press.mantra.compose.repository.ChatRepository +import press.mantra.compose.repository.DkgRepository +import press.mantra.compose.repository.FrostSigningRepository +import press.mantra.compose.repository.NostrRepository +import press.mantra.compose.ui.composable.widgets.ProvideSnackbarHost +import press.mantra.compose.ui.theme.MantraTheme +import press.mantra.compose.ui.view.state.ChatRoomMessagingUIState +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Where the back button is, on the two screens that have to decide. + * + * Every other pushed screen draws `NavigateBackButton` unconditionally, and there is + * nothing to assert about that beyond the compiler: the parameter is not optional. These + * two take a nullable callback because the same composable is reached two ways, and + * only the caller knows which: + * + * - the chat room is a destination on a phone and the home screen's detail pane in an + * expanded window. In the pane the room list is beside it, and a button that popped + * would pop the home screen; + * - the "coming soon" placeholder is pushed from a *learn more* and is also where the + * navigation machine lands, with the stack cleared, on an error. + * + * Read by the description a screen reader would announce, which is also the one thing + * about the button a test can check that the type system does not. + */ +@OptIn(ExperimentalTestApi::class) +class NavigateBackButtonJvmTest { + + @Test + fun `a pushed chat room has a back button, and it pops`() = runDesktopComposeUiTest(400, 900) { + var popped = false + setContent { Chat(onNavigateBack = { popped = true }) } + + onNodeWithContentDescription("Back").assertIsDisplayed().performClick() + assertTrue(popped, "the button should have called the host's pop") + } + + @Test + fun `the same chat room beside its list has none`() = runDesktopComposeUiTest(1000, 900) { + setContent { Chat(onNavigateBack = null) } + + onNodeWithContentDescription("Back").assertDoesNotExist() + } + + @Test + fun `a pushed placeholder has a back button`() = runDesktopComposeUiTest(400, 900) { + setContent { Placeholder(onNavigateBack = {}) } + + onNodeWithContentDescription("Back").assertIsDisplayed() + } + + @Test + fun `a placeholder at the root of the stack has none`() = runDesktopComposeUiTest(400, 900) { + setContent { Placeholder(onNavigateBack = null) } + + onNodeWithContentDescription("Back").assertDoesNotExist() + } + + @Composable + private fun Chat(onNavigateBack: (() -> Unit)?) { + MantraTheme { + ProvideSnackbarHost { + ChatRoomMessagingScreen( + activeUserPublicKey = "a".repeat(64), + chatRoomId = "b".repeat(64), + relayHint = null, + initialChatRoomMessagingUIState = ChatRoomMessagingUIState.Loaded( + localChatRoom = LocalChatRoom( + chatRoom = ChatRoom( + id = "b".repeat(64), + userPublicKey = "a".repeat(64), + subject = "Reading group", + description = null, + initialGiftWrapPayloadId = null, + mlsGroupState = null, + ), + ), + ), + nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY, + chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, + frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, + dkgRepository = DkgRepository.NO_OP_DKG_REPOSITORY, + onNavigateToRouteAndPopUpInclusive = {}, + onNavigateToRoute = {}, + onNavigateBack = onNavigateBack, + ) + } + } + } + + @Composable + private fun Placeholder(onNavigateBack: (() -> Unit)?) { + MantraTheme { + ProvideSnackbarHost { + ImplementationPendingScreen(text = "Learn more", onNavigateBack = onNavigateBack) + } + } + } +} diff --git a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/ProposeGroupEventScreenJvmTest.kt b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/ProposeGroupEventScreenJvmTest.kt index 6b471f77..19d7afd4 100644 --- a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/ProposeGroupEventScreenJvmTest.kt +++ b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/composable/ProposeGroupEventScreenJvmTest.kt @@ -104,7 +104,8 @@ class ProposeGroupEventScreenJvmTest { chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY, - onNavigateToRouteAndPopUpInclusive = {} + onNavigateToRouteAndPopUpInclusive = {}, + onNavigateBack = {}, ) } }