diff --git a/app/src/main/kotlin/net/primal/android/articles/reads/ReadsScreen.kt b/app/src/main/kotlin/net/primal/android/articles/reads/ReadsScreen.kt index 12aa5e55b..451e18255 100644 --- a/app/src/main/kotlin/net/primal/android/articles/reads/ReadsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/articles/reads/ReadsScreen.kt @@ -52,6 +52,7 @@ import net.primal.android.drawer.PrimalDrawerScaffold import net.primal.android.drawer.multiaccount.events.AccountSwitcherCallbacks import net.primal.android.feeds.list.FeedsBottomSheet import net.primal.android.feeds.list.ui.model.FeedUi +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.premium.legend.domain.LegendaryCustomization import net.primal.domain.feeds.FeedSpecKind import net.primal.domain.links.CdnImage @@ -63,6 +64,7 @@ fun ReadsScreen( onDrawerScreenClick: (DrawerScreenDestination) -> Unit, accountSwitcherCallbacks: AccountSwitcherCallbacks, callbacks: ReadsScreenContract.ScreenCallbacks, + navigator: PrimalNavigator, ) { val uiState = viewModel.state.collectAsState() @@ -73,6 +75,7 @@ fun ReadsScreen( eventPublisher = viewModel::setEvent, accountSwitcherCallbacks = accountSwitcherCallbacks, callbacks = callbacks, + navigator = navigator, ) } @@ -85,6 +88,7 @@ private fun ReadsScreen( eventPublisher: (ReadsScreenContract.UiEvent) -> Unit, accountSwitcherCallbacks: AccountSwitcherCallbacks, callbacks: ReadsScreenContract.ScreenCallbacks, + navigator: PrimalNavigator, ) { val context = LocalContext.current val uiScope = rememberCoroutineScope() @@ -118,7 +122,7 @@ private fun ReadsScreen( accountSwitcherCallbacks = accountSwitcherCallbacks, onPrimaryDestinationChanged = onTopLevelDestinationChanged, onDrawerDestinationClick = onDrawerScreenClick, - onDrawerQrCodeClick = callbacks.onDrawerQrCodeClick, + onDrawerQrCodeClick = { callbacks.onDrawerQrCodeClick() }, badges = state.badges, focusModeEnabled = LocalContentDisplaySettings.current.focusModeEnabled, topAppBar = { scrollBehavior -> @@ -129,12 +133,13 @@ private fun ReadsScreen( avatarLegendaryCustomization = state.activeAccountLegendaryCustomization, avatarBlossoms = state.activeAccountBlossoms, onAvatarClick = { uiScope.launch { drawerState.open() } }, - onSearchClick = callbacks.onSearchClick, + onSearchClick = { callbacks.onSearchClick() }, onFeedChanged = { feed -> val pageIndex = state.feeds.indexOf(feed) uiScope.launch { pagerState.scrollToPage(page = pageIndex) } }, scrollBehavior = scrollBehavior, + navigator = navigator, ) }, content = { paddingValues -> @@ -151,8 +156,8 @@ private fun ReadsScreen( feedSpec = state.feeds[index].spec, contentPadding = paddingValues, shouldAnimateScrollToTop = shouldAnimateScrollToTop, - onArticleClick = callbacks.onArticleClick, - onGetPremiumClick = callbacks.onGetPremiumClick, + onArticleClick = { callbacks.onArticleClick(it) }, + onGetPremiumClick = { callbacks.onGetPremiumClick() }, onUiError = { uiError: UiError -> uiScope.launch { snackbarHostState.showSnackbar( @@ -196,6 +201,7 @@ private fun ArticleFeedTopAppBar( avatarLegendaryCustomization: LegendaryCustomization? = null, avatarBlossoms: List = emptyList(), scrollBehavior: TopAppBarScrollBehavior? = null, + navigator: PrimalNavigator, ) { var feedPickerVisible by rememberSaveable { mutableStateOf(false) } @@ -209,6 +215,7 @@ private fun ArticleFeedTopAppBar( }, onDismissRequest = { feedPickerVisible = false }, sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + navigator = navigator, ) } diff --git a/app/src/main/kotlin/net/primal/android/articles/reads/ReadsScreenContract.kt b/app/src/main/kotlin/net/primal/android/articles/reads/ReadsScreenContract.kt index b35351024..81658c049 100644 --- a/app/src/main/kotlin/net/primal/android/articles/reads/ReadsScreenContract.kt +++ b/app/src/main/kotlin/net/primal/android/articles/reads/ReadsScreenContract.kt @@ -23,7 +23,7 @@ interface ReadsScreenContract { data class ScreenCallbacks( val onDrawerQrCodeClick: () -> Unit, val onSearchClick: () -> Unit, - val onArticleClick: (String) -> Unit, + val onArticleClick: (article: String) -> Unit, val onGetPremiumClick: () -> Unit, ) } diff --git a/app/src/main/kotlin/net/primal/android/bookmarks/list/BookmarksScreen.kt b/app/src/main/kotlin/net/primal/android/bookmarks/list/BookmarksScreen.kt index d40713ece..3b2ac4f06 100644 --- a/app/src/main/kotlin/net/primal/android/bookmarks/list/BookmarksScreen.kt +++ b/app/src/main/kotlin/net/primal/android/bookmarks/list/BookmarksScreen.kt @@ -34,15 +34,15 @@ import net.primal.android.core.compose.icons.PrimalIcons import net.primal.android.core.compose.icons.primaliconpack.ArrowBack import net.primal.android.core.errors.UiError import net.primal.android.core.errors.resolveUiErrorMessage +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.list.NoteFeedList -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.theme.AppTheme import net.primal.domain.feeds.FeedSpecKind @Composable fun BookmarksScreen( viewModel: BookmarksViewModel, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, callbacks: BookmarksContract.ScreenCallbacks, ) { val uiState = viewModel.state.collectAsState() @@ -50,7 +50,7 @@ fun BookmarksScreen( BookmarksScreen( state = uiState.value, eventPublisher = viewModel::setEvent, - noteCallbacks = noteCallbacks, + navigator = navigator, callbacks = callbacks, ) } @@ -59,7 +59,7 @@ fun BookmarksScreen( private fun BookmarksScreen( state: BookmarksContract.UiState, eventPublisher: (BookmarksContract.UiEvent) -> Unit, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, callbacks: BookmarksContract.ScreenCallbacks, ) { val uiScope = rememberCoroutineScope() @@ -87,10 +87,10 @@ private fun BookmarksScreen( ArticleFeedList( contentPadding = paddingValues, feedSpec = state.feedSpec, - onArticleClick = { naddr -> noteCallbacks.onArticleClick?.invoke(naddr) }, + onArticleClick = { naddr -> navigator.onArticleClick(naddr) }, pullToRefreshEnabled = false, noContentText = stringResource(R.string.bookmarks_no_content), - onGetPremiumClick = { noteCallbacks.onGetPrimalPremiumClick?.invoke() }, + onGetPremiumClick = { navigator.onGetPrimalPremiumClick() }, onUiError = { uiError: UiError -> uiScope.launch { snackbarHostState.showSnackbar( @@ -106,7 +106,7 @@ private fun BookmarksScreen( NoteFeedList( contentPadding = paddingValues, feedSpec = state.feedSpec, - noteCallbacks = noteCallbacks, + navigator = navigator, onGoToWallet = callbacks.onGoToWallet, pollingEnabled = false, pullToRefreshEnabled = false, diff --git a/app/src/main/kotlin/net/primal/android/core/compose/zaps/ReferencedNoteZap.kt b/app/src/main/kotlin/net/primal/android/core/compose/zaps/ReferencedNoteZap.kt index bbed8f44d..aecc2f50b 100644 --- a/app/src/main/kotlin/net/primal/android/core/compose/zaps/ReferencedNoteZap.kt +++ b/app/src/main/kotlin/net/primal/android/core/compose/zaps/ReferencedNoteZap.kt @@ -27,9 +27,9 @@ import net.primal.android.core.compose.UniversalAvatarThumbnail import net.primal.android.core.compose.asBeforeNowFormat import net.primal.android.core.compose.icons.PrimalIcons import net.primal.android.core.compose.icons.primaliconpack.LightningBoltFilled +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.model.NoteContentUi import net.primal.android.notes.feed.note.ui.NoteContent -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.premium.legend.domain.LegendaryCustomization import net.primal.android.theme.AppTheme import net.primal.domain.links.CdnImage @@ -41,7 +41,7 @@ fun ReferencedNoteZap( noteContentUi: NoteContentUi, amountInSats: ULong, createdAt: Instant, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, message: String?, receiverDisplayName: String?, modifier: Modifier = Modifier, @@ -56,7 +56,7 @@ fun ReferencedNoteZap( .clickable( interactionSource = null, indication = null, - onClick = { noteCallbacks.onNoteClick?.invoke(noteContentUi.noteId) }, + onClick = { navigator.onNoteClick(noteContentUi.noteId) }, ) .background(AppTheme.extraColorScheme.surfaceVariantAlt3) .padding(all = 8.dp), @@ -64,7 +64,7 @@ fun ReferencedNoteZap( verticalArrangement = Arrangement.spacedBy(12.dp), ) { ZapHeader( - onSenderAvatarClick = { senderId?.let { noteCallbacks.onProfileClick?.invoke(senderId) } }, + onSenderAvatarClick = { senderId?.let { navigator.onProfileClick(senderId) } }, senderCdnImage = senderAvatarCdnImage, amountSats = amountInSats, message = message, @@ -73,11 +73,11 @@ fun ReferencedNoteZap( NoteSummary( noteContent = noteContentUi, noteTimestamp = createdAt, - noteCallbacks = noteCallbacks, - onNoteClick = { noteCallbacks.onNoteClick?.invoke(it) }, + navigator = navigator, + onNoteClick = { navigator.onNoteClick(it) }, receiverCdnResource = receiverAvatarCdnImage, receiverDisplayName = receiverDisplayName, - onReceiverAvatarClick = { receiverId?.let { noteCallbacks.onProfileClick?.invoke(receiverId) } }, + onReceiverAvatarClick = { receiverId?.let { navigator.onProfileClick(receiverId) } }, receiverLegendaryCustomization = receiverLegendaryCustomization, ) } @@ -90,7 +90,7 @@ private fun NoteSummary( receiverCdnResource: CdnImage?, receiverLegendaryCustomization: LegendaryCustomization?, noteTimestamp: Instant, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, onReceiverAvatarClick: () -> Unit, onNoteClick: (String) -> Unit, ) { @@ -133,9 +133,7 @@ private fun NoteSummary( } NoteContent( expanded = false, - noteCallbacks = noteCallbacks.copy( - onProfileClick = null, - ), + navigator = navigator, data = noteContent, contentColor = AppTheme.extraColorScheme.onSurfaceVariantAlt3, highlightColor = AppTheme.extraColorScheme.onSurfaceVariantAlt3, diff --git a/app/src/main/kotlin/net/primal/android/core/compose/zaps/ReferencedZap.kt b/app/src/main/kotlin/net/primal/android/core/compose/zaps/ReferencedZap.kt index a4af91ce6..6f205ebb8 100644 --- a/app/src/main/kotlin/net/primal/android/core/compose/zaps/ReferencedZap.kt +++ b/app/src/main/kotlin/net/primal/android/core/compose/zaps/ReferencedZap.kt @@ -25,7 +25,7 @@ import net.primal.android.core.compose.UniversalAvatarThumbnail import net.primal.android.core.compose.icons.PrimalIcons import net.primal.android.core.compose.icons.primaliconpack.LightningBoltFilled import net.primal.android.core.compose.preview.PrimalPreview -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks +import net.primal.android.navigation.interactions.ContentInteractionCallbacks import net.primal.android.premium.legend.domain.asLegendaryCustomization import net.primal.android.theme.AppTheme import net.primal.android.theme.domain.PrimalTheme @@ -35,7 +35,6 @@ import net.primal.domain.premium.PrimalLegendProfile @Composable fun ReferencedZap( modifier: Modifier = Modifier, - noteCallbacks: NoteCallbacks, senderId: String, receiverId: String, amountInSats: Double, @@ -45,6 +44,7 @@ fun ReferencedZap( receiverDisplayName: String?, receiverAvatarCdnImage: CdnImage? = null, receiverPrimalLegendProfile: PrimalLegendProfile? = null, + contentInteractionCallbacks: ContentInteractionCallbacks, ) { Row( modifier = modifier @@ -67,7 +67,7 @@ fun ReferencedZap( avatarSize = 36.dp, avatarCdnImage = senderAvatarCdnImage, legendaryCustomization = senderPrimalLegendProfile?.asLegendaryCustomization(), - onClick = { noteCallbacks.onProfileClick?.invoke(senderId) }, + onClick = { contentInteractionCallbacks.onProfileClick(senderId) }, ) ZapAmountAndMessageColumn( @@ -79,7 +79,7 @@ fun ReferencedZap( avatarSize = 36.dp, avatarCdnImage = receiverAvatarCdnImage, legendaryCustomization = receiverPrimalLegendProfile?.asLegendaryCustomization(), - onClick = { noteCallbacks.onProfileClick?.invoke(receiverId) }, + onClick = { contentInteractionCallbacks.onProfileClick(receiverId) }, ) } @@ -141,7 +141,10 @@ fun PreviewMessageAndDisplayName() { PrimalPreview(primalTheme = PrimalTheme.Sunset) { ReferencedZap( modifier = Modifier.width(300.dp), - noteCallbacks = NoteCallbacks(), + contentInteractionCallbacks = object : ContentInteractionCallbacks { + override fun onProfileClick(profileId: String) = Unit + override fun onHashtagClick(hashtag: String) = Unit + }, senderId = "", senderAvatarCdnImage = null, senderPrimalLegendProfile = null, @@ -161,7 +164,10 @@ fun PreviewNoMessageAndDisplayName() { PrimalPreview(primalTheme = PrimalTheme.Sunset) { ReferencedZap( modifier = Modifier.width(300.dp), - noteCallbacks = NoteCallbacks(), + contentInteractionCallbacks = object : ContentInteractionCallbacks { + override fun onProfileClick(profileId: String) = Unit + override fun onHashtagClick(hashtag: String) = Unit + }, senderId = "", senderAvatarCdnImage = null, senderPrimalLegendProfile = null, @@ -181,7 +187,10 @@ fun PreviewNoMessageAndNoDisplayName() { PrimalPreview(primalTheme = PrimalTheme.Sunset) { ReferencedZap( modifier = Modifier.width(300.dp), - noteCallbacks = NoteCallbacks(), + contentInteractionCallbacks = object : ContentInteractionCallbacks { + override fun onProfileClick(profileId: String) = Unit + override fun onHashtagClick(hashtag: String) = Unit + }, senderId = "", senderAvatarCdnImage = null, senderPrimalLegendProfile = null, @@ -201,7 +210,10 @@ fun PreviewMessageAndNoDisplayName() { PrimalPreview(primalTheme = PrimalTheme.Sunset) { ReferencedZap( modifier = Modifier.width(300.dp), - noteCallbacks = NoteCallbacks(), + contentInteractionCallbacks = object : ContentInteractionCallbacks { + override fun onProfileClick(profileId: String) = Unit + override fun onHashtagClick(hashtag: String) = Unit + }, senderId = "", senderAvatarCdnImage = null, senderPrimalLegendProfile = null, diff --git a/app/src/main/kotlin/net/primal/android/editor/NoteEditorScreen.kt b/app/src/main/kotlin/net/primal/android/editor/NoteEditorScreen.kt index 9d4034d7b..3c25456f0 100644 --- a/app/src/main/kotlin/net/primal/android/editor/NoteEditorScreen.kt +++ b/app/src/main/kotlin/net/primal/android/editor/NoteEditorScreen.kt @@ -91,6 +91,7 @@ import net.primal.android.editor.domain.NoteAttachment import net.primal.android.editor.ui.NoteAttachmentPreview import net.primal.android.editor.ui.NoteOutlinedTextField import net.primal.android.editor.ui.NoteTagUserLazyColumn +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.nostr.mappers.toReferencedHighlight import net.primal.android.notes.feed.model.FeedPostUi import net.primal.android.notes.feed.model.toNoteContentUi @@ -101,14 +102,16 @@ import net.primal.android.notes.feed.note.ui.NoteUnknownEvent import net.primal.android.notes.feed.note.ui.ReferencedArticleCard import net.primal.android.notes.feed.note.ui.ReferencedHighlight import net.primal.android.notes.feed.note.ui.ReferencedNoteCard -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.theme.AppTheme import net.primal.domain.nostr.asATagValue @Composable -fun NoteEditorScreen(viewModel: NoteEditorViewModel, callbacks: NoteEditorContract.ScreenCallbacks) { +fun NoteEditorScreen( + viewModel: NoteEditorViewModel, + callbacks: NoteEditorContract.ScreenCallbacks, + navigator: PrimalNavigator, +) { val uiState = viewModel.state.collectAsState() - LaunchedEffect(viewModel, callbacks) { viewModel.effect.collect { when (it) { @@ -121,6 +124,7 @@ fun NoteEditorScreen(viewModel: NoteEditorViewModel, callbacks: NoteEditorContra state = uiState.value, callbacks = callbacks, eventPublisher = { viewModel.setEvent(it) }, + navigator = navigator, ) } @@ -130,6 +134,7 @@ fun NoteEditorScreen( state: NoteEditorContract.UiState, callbacks: NoteEditorContract.ScreenCallbacks, eventPublisher: (UiEvent) -> Unit, + navigator: PrimalNavigator, ) { val snackbarHostState = remember { SnackbarHostState() } val context = LocalContext.current @@ -146,7 +151,7 @@ fun NoteEditorScreen( PrimalTopAppBar( title = "", navigationIcon = Icons.Outlined.Close, - onNavigationIconClick = callbacks.onClose, + onNavigationIconClick = { callbacks.onClose() }, navigationIconContentDescription = stringResource(id = R.string.accessibility_close), showDivider = true, actions = { @@ -178,7 +183,7 @@ fun NoteEditorScreen( state = state, eventPublisher = eventPublisher, contentPadding = paddingValues, - noteCallbacks = NoteCallbacks(), + navigator = navigator, ) }, ) @@ -213,7 +218,7 @@ private fun NoteEditorBox( eventPublisher: (UiEvent) -> Unit, modifier: Modifier = Modifier, contentPadding: PaddingValues, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, ) { val editorListState = rememberLazyListState() var noteEditorMaxHeightPx by remember { mutableIntStateOf(0) } @@ -253,6 +258,7 @@ private fun NoteEditorBox( referencedHighlight = state.replyToHighlight, referencedArticle = state.replyToArticle, conversation = state.replyToConversation, + navigator = navigator, ) } @@ -269,7 +275,7 @@ private fun NoteEditorBox( nostrUris( nostrUris = state.referencedNostrUris, - noteCallbacks = noteCallbacks, + navigator = navigator, onRetryUriClick = { eventPublisher(UiEvent.RefreshUri(it.uri)) }, onRemoveUriClick = { eventPublisher(UiEvent.RemoveUri(it)) }, onRemoveHighlight = { eventPublisher(UiEvent.RemoveHighlightByArticle(it)) }, @@ -322,6 +328,7 @@ private fun LazyListScope.referencedEventsAndConversationAsReplyTo( referencedArticle: FeedArticleUi?, referencedHighlight: HighlightUi?, conversation: List = emptyList(), + navigator: PrimalNavigator, ) { if (referencedHighlight != null) { item( @@ -361,6 +368,7 @@ private fun LazyListScope.referencedEventsAndConversationAsReplyTo( ReplyToNote( replyToNote = it, connectionLineColor = AppTheme.colorScheme.outline, + navigator = navigator, ) } } @@ -374,7 +382,7 @@ fun LazyListScope.nostrUris( onRemoveUriClick: (Int) -> Unit, onRemoveHighlight: (String) -> Unit, nostrUris: List>, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, ) { items( count = nostrUris.size, @@ -403,7 +411,7 @@ fun LazyListScope.nostrUris( uri.data?.let { note -> ReferencedNoteCard( data = note, - noteCallbacks = noteCallbacks, + navigator = navigator, ) } } @@ -651,7 +659,11 @@ private fun NoteAttachmentsLazyRow( } @Composable -private fun ReplyToNote(replyToNote: FeedPostUi, connectionLineColor: Color) { +private fun ReplyToNote( + replyToNote: FeedPostUi, + connectionLineColor: Color, + navigator: PrimalNavigator, +) { Column( modifier = Modifier.drawWithCache { onDrawBehind { @@ -697,7 +709,7 @@ private fun ReplyToNote(replyToNote: FeedPostUi, connectionLineColor: Color) { expanded = true, onClick = {}, onUrlClick = {}, - noteCallbacks = NoteCallbacks(), + navigator = navigator, ) } } diff --git a/app/src/main/kotlin/net/primal/android/explore/feed/ExploreFeedScreen.kt b/app/src/main/kotlin/net/primal/android/explore/feed/ExploreFeedScreen.kt index 6b2241def..43d217ffd 100644 --- a/app/src/main/kotlin/net/primal/android/explore/feed/ExploreFeedScreen.kt +++ b/app/src/main/kotlin/net/primal/android/explore/feed/ExploreFeedScreen.kt @@ -45,9 +45,9 @@ import net.primal.android.explore.feed.ExploreFeedContract.UiEvent.RemoveFromUse import net.primal.android.explore.feed.ExploreFeedContract.UiState.ExploreFeedError import net.primal.android.feeds.resolveDefaultDescription import net.primal.android.feeds.resolveDefaultTitle +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.grid.MediaFeedGrid import net.primal.android.notes.feed.list.NoteFeedList -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.theme.AppTheme import net.primal.domain.feeds.FeedSpecKind import net.primal.domain.feeds.extractTopicFromFeedSpec @@ -56,14 +56,14 @@ import net.primal.domain.feeds.isSearchFeedSpec @Composable fun ExploreFeedScreen( viewModel: ExploreFeedViewModel, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, callbacks: ExploreFeedContract.ScreenCallbacks, ) { val uiState = viewModel.state.collectAsState() ExploreFeedScreen( state = uiState.value, - noteCallbacks = noteCallbacks, + navigator = navigator, callbacks = callbacks, eventPublisher = { viewModel.setEvent(it) }, ) @@ -73,7 +73,7 @@ fun ExploreFeedScreen( @Composable fun ExploreFeedScreen( state: ExploreFeedContract.UiState, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, callbacks: ExploreFeedContract.ScreenCallbacks, eventPublisher: (ExploreFeedContract.UiEvent) -> Unit, ) { @@ -148,15 +148,15 @@ fun ExploreFeedScreen( when (state.feedSpecKind) { FeedSpecKind.Reads -> ExploreArticleFeed( feedSpec = state.feedSpec, - onArticleClick = { noteCallbacks.onArticleClick?.invoke(it) }, - onGetPremiumClick = { noteCallbacks.onGetPrimalPremiumClick?.invoke() }, + onArticleClick = { navigator.onArticleClick(it) }, + onGetPremiumClick = { navigator.onGetPrimalPremiumClick() }, contentPadding = paddingValues, ) else -> ExploreNoteFeed( feedSpec = state.feedSpec, renderType = state.renderType, - noteCallbacks = noteCallbacks, + navigator = navigator, contentPadding = paddingValues, onGoToWallet = callbacks.onGoToWallet, onUiError = { uiError -> @@ -209,7 +209,7 @@ private fun ExploreNoteFeed( feedSpec: String, renderType: ExploreFeedContract.RenderType, contentPadding: PaddingValues, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, onGoToWallet: () -> Unit, onUiError: ((UiError) -> Unit)? = null, ) { @@ -217,7 +217,7 @@ private fun ExploreNoteFeed( ExploreFeedContract.RenderType.List -> { NoteFeedList( feedSpec = feedSpec, - noteCallbacks = noteCallbacks, + navigator = navigator, onGoToWallet = onGoToWallet, contentPadding = contentPadding, onUiError = onUiError, @@ -229,8 +229,8 @@ private fun ExploreNoteFeed( MediaFeedGrid( feedSpec = feedSpec, contentPadding = contentPadding, - onNoteClick = { noteCallbacks.onNoteClick?.invoke(it) }, - onGetPrimalPremiumClick = { noteCallbacks.onGetPrimalPremiumClick?.invoke() }, + onNoteClick = { navigator.onNoteClick(it) }, + onGetPrimalPremiumClick = { navigator.onGetPrimalPremiumClick() }, ) } } diff --git a/app/src/main/kotlin/net/primal/android/explore/home/ExploreHomeScreen.kt b/app/src/main/kotlin/net/primal/android/explore/home/ExploreHomeScreen.kt index a742f558e..8332e44dd 100644 --- a/app/src/main/kotlin/net/primal/android/explore/home/ExploreHomeScreen.kt +++ b/app/src/main/kotlin/net/primal/android/explore/home/ExploreHomeScreen.kt @@ -75,8 +75,8 @@ import net.primal.android.explore.home.ui.PEOPLE_INDEX import net.primal.android.explore.home.ui.TOPICS_INDEX import net.primal.android.explore.home.ui.ZAPS_INDEX import net.primal.android.explore.home.zaps.ExploreZaps +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.grid.MediaFeedGrid -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.premium.legend.domain.LegendaryCustomization import net.primal.android.theme.AppTheme import net.primal.android.theme.domain.PrimalTheme @@ -88,9 +88,9 @@ fun ExploreHomeScreen( viewModel: ExploreHomeViewModel, onTopLevelDestinationChanged: (PrimalTopLevelDestination) -> Unit, onDrawerScreenClick: (DrawerScreenDestination) -> Unit, - noteCallbacks: NoteCallbacks, accountSwitcherCallbacks: AccountSwitcherCallbacks, callbacks: ExploreHomeContract.ScreenCallbacks, + navigator: PrimalNavigator, ) { val uiState = viewModel.state.collectAsState() @@ -98,9 +98,9 @@ fun ExploreHomeScreen( state = uiState.value, onPrimaryDestinationChanged = onTopLevelDestinationChanged, onDrawerDestinationClick = onDrawerScreenClick, - noteCallbacks = noteCallbacks, accountSwitcherCallbacks = accountSwitcherCallbacks, callbacks = callbacks, + navigator = navigator, ) } @@ -110,9 +110,9 @@ private fun ExploreHomeScreen( state: ExploreHomeContract.UiState, onPrimaryDestinationChanged: (PrimalTopLevelDestination) -> Unit, onDrawerDestinationClick: (DrawerScreenDestination) -> Unit, - noteCallbacks: NoteCallbacks, accountSwitcherCallbacks: AccountSwitcherCallbacks, callbacks: ExploreHomeContract.ScreenCallbacks, + navigator: PrimalNavigator, ) { val context = LocalContext.current val uiScope = rememberCoroutineScope() @@ -128,7 +128,7 @@ private fun ExploreHomeScreen( activeDestination = PrimalTopLevelDestination.Explore, onPrimaryDestinationChanged = onPrimaryDestinationChanged, onDrawerDestinationClick = onDrawerDestinationClick, - onDrawerQrCodeClick = callbacks.onDrawerQrCodeClick, + onDrawerQrCodeClick = { callbacks.onDrawerQrCodeClick() }, badges = state.badges, focusModeEnabled = LocalContentDisplaySettings.current.focusModeEnabled, topAppBarState = topAppBarState, @@ -144,8 +144,8 @@ private fun ExploreHomeScreen( onNavigationIconClick = { uiScope.launch { drawerState.open() } }, - onSearchClick = callbacks.onSearchClick, - onActionIconClick = callbacks.onAdvancedSearchClick, + onSearchClick = { callbacks.onSearchClick() }, + onActionIconClick = { callbacks.onAdvancedSearchClick() }, scrollBehavior = topAppBarScrollBehavior, ) }, @@ -158,8 +158,10 @@ private fun ExploreHomeScreen( ExplorePeople( modifier = Modifier.background(color = AppTheme.colorScheme.surfaceVariant), paddingValues = paddingValues, - onProfileClick = { noteCallbacks.onProfileClick?.invoke(it) }, - onFollowPackClick = callbacks.onFollowPackClick, + onProfileClick = { navigator.onProfileClick(it) }, + onFollowPackClick = { profileId, identifier -> + callbacks.onFollowPackClick(profileId, identifier) + }, ) } @@ -167,7 +169,7 @@ private fun ExploreHomeScreen( ExploreFeeds( modifier = Modifier.background(color = AppTheme.colorScheme.surfaceVariant), paddingValues = paddingValues, - onGoToWallet = callbacks.onGoToWallet, + onGoToWallet = { callbacks.onGoToWallet() }, onUiError = { uiError: UiError -> uiScope.launch { snackbarHostState.showSnackbar( @@ -176,6 +178,7 @@ private fun ExploreHomeScreen( ) } }, + navigator = navigator, ) } @@ -183,7 +186,7 @@ private fun ExploreHomeScreen( ExploreZaps( modifier = Modifier.background(color = AppTheme.colorScheme.surfaceVariant), paddingValues = paddingValues, - noteCallbacks = noteCallbacks, + navigator = navigator, ) } @@ -191,8 +194,8 @@ private fun ExploreHomeScreen( MediaFeedGrid( feedSpec = exploreMediaFeedSpec, contentPadding = paddingValues, - onNoteClick = { noteCallbacks.onNoteClick?.invoke(it) }, - onGetPrimalPremiumClick = { noteCallbacks.onGetPrimalPremiumClick?.invoke() }, + onNoteClick = { navigator.onNoteClick(it) }, + onGetPrimalPremiumClick = { navigator.onGetPrimalPremiumClick() }, ) } @@ -200,7 +203,7 @@ private fun ExploreHomeScreen( ExploreTopics( modifier = Modifier.background(color = AppTheme.colorScheme.surfaceVariant), paddingValues = paddingValues, - onHashtagClick = { noteCallbacks.onHashtagClick?.invoke(it) }, + onHashtagClick = { navigator.onHashtagClick(it) }, ) } } @@ -210,7 +213,7 @@ private fun ExploreHomeScreen( SnackbarHost(hostState = snackbarHostState) }, floatingActionButton = { - NewPostFloatingActionButton(onNewPostClick = callbacks.onNewPostClick) + NewPostFloatingActionButton(onNewPostClick = { callbacks.onNewPostClick() }) }, ) } diff --git a/app/src/main/kotlin/net/primal/android/explore/home/feeds/ExploreFeeds.kt b/app/src/main/kotlin/net/primal/android/explore/home/feeds/ExploreFeeds.kt index f6727ef49..948ced673 100644 --- a/app/src/main/kotlin/net/primal/android/explore/home/feeds/ExploreFeeds.kt +++ b/app/src/main/kotlin/net/primal/android/explore/home/feeds/ExploreFeeds.kt @@ -44,6 +44,7 @@ import net.primal.android.core.errors.UiError import net.primal.android.feeds.dvm.ui.DvmFeedListItem import net.primal.android.feeds.dvm.ui.DvmFeedUi import net.primal.android.feeds.dvm.ui.DvmHeaderAndFeedList +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.theme.AppTheme import net.primal.domain.feeds.buildSpec @@ -53,6 +54,7 @@ fun ExploreFeeds( paddingValues: PaddingValues = PaddingValues(all = 0.dp), onGoToWallet: (() -> Unit)? = null, onUiError: ((UiError) -> Unit)? = null, + navigator: PrimalNavigator, ) { val viewModel: ExploreFeedsViewModel = hiltViewModel() val uiState = viewModel.state.collectAsState() @@ -64,6 +66,7 @@ fun ExploreFeeds( eventPublisher = viewModel::setEvent, onGoToWallet = onGoToWallet, onUiError = onUiError, + navigator = navigator, ) } @@ -75,6 +78,7 @@ fun ExploreFeeds( eventPublisher: (ExploreFeedsContract.UiEvent) -> Unit, onGoToWallet: (() -> Unit)? = null, onUiError: ((UiError) -> Unit)? = null, + navigator: PrimalNavigator, ) { var dvmFeedToShow by remember { mutableStateOf(null) } @@ -96,6 +100,7 @@ fun ExploreFeeds( removeFromUserFeeds = { eventPublisher(ExploreFeedsContract.UiEvent.RemoveFromUserFeeds(it)) }, onGoToWallet = onGoToWallet, onUiError = onUiError, + navigator = navigator, ) } @@ -152,6 +157,7 @@ private fun DvmFeedDetailsBottomSheet( addedToFeed: Boolean, addToUserFeeds: (DvmFeedUi) -> Unit, removeFromUserFeeds: (DvmFeedUi) -> Unit, + navigator: PrimalNavigator, ) { val scope = rememberCoroutineScope() val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) @@ -212,6 +218,7 @@ private fun DvmFeedDetailsBottomSheet( showFollowsActionsAvatarRow = true, clipShape = null, onUiError = onUiError, + navigator = navigator, ) } } diff --git a/app/src/main/kotlin/net/primal/android/explore/home/zaps/ExploreZaps.kt b/app/src/main/kotlin/net/primal/android/explore/home/zaps/ExploreZaps.kt index f34f7c942..bc29b1462 100644 --- a/app/src/main/kotlin/net/primal/android/explore/home/zaps/ExploreZaps.kt +++ b/app/src/main/kotlin/net/primal/android/explore/home/zaps/ExploreZaps.kt @@ -18,14 +18,14 @@ import net.primal.android.R import net.primal.android.core.compose.HeightAdjustableLoadingLazyListPlaceholder import net.primal.android.core.compose.ListNoContent import net.primal.android.core.compose.zaps.ReferencedNoteZap -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.theme.AppTheme @Composable fun ExploreZaps( modifier: Modifier = Modifier, paddingValues: PaddingValues, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, ) { val viewModel: ExploreZapsViewModel = hiltViewModel() val uiState = viewModel.state.collectAsState() @@ -34,8 +34,8 @@ fun ExploreZaps( modifier = modifier, state = uiState.value, paddingValues = paddingValues, - noteCallbacks = noteCallbacks, eventPublisher = viewModel::setEvent, + navigator = navigator, ) } @@ -43,7 +43,7 @@ fun ExploreZaps( private fun ExploreZaps( modifier: Modifier = Modifier, paddingValues: PaddingValues, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, state: ExploreZapsContract.UiState, eventPublisher: (ExploreZapsContract.UiEvent) -> Unit, ) { @@ -86,7 +86,7 @@ private fun ExploreZaps( receiverDisplayName = item.receiver?.authorDisplayName, receiverAvatarCdnImage = item.receiver?.avatarCdnImage, receiverLegendaryCustomization = item.receiver?.premiumDetails?.legendaryCustomization, - noteCallbacks = noteCallbacks, + navigator = navigator, ) } item { Spacer(modifier = Modifier.height(4.dp)) } diff --git a/app/src/main/kotlin/net/primal/android/feeds/dvm/ui/DvmHeaderAndFeedList.kt b/app/src/main/kotlin/net/primal/android/feeds/dvm/ui/DvmHeaderAndFeedList.kt index 40f9f33e7..6b36f14d6 100644 --- a/app/src/main/kotlin/net/primal/android/feeds/dvm/ui/DvmHeaderAndFeedList.kt +++ b/app/src/main/kotlin/net/primal/android/feeds/dvm/ui/DvmHeaderAndFeedList.kt @@ -6,8 +6,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape import net.primal.android.articles.feed.ArticleFeedList import net.primal.android.core.errors.UiError +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.list.NoteFeedList -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.theme.AppTheme import net.primal.domain.feeds.FeedSpecKind import net.primal.domain.feeds.buildSpec @@ -21,6 +21,7 @@ fun DvmHeaderAndFeedList( clipShape: Shape? = AppTheme.shapes.small, onGoToWallet: (() -> Unit)? = null, onUiError: ((UiError) -> Unit)? = null, + navigator: PrimalNavigator, ) { Column(modifier = modifier) { val feedSpecKind = dvmFeed.data.kind @@ -49,7 +50,7 @@ fun DvmHeaderAndFeedList( FeedSpecKind.Notes -> { NoteFeedList( feedSpec = dvmFeed.data.buildSpec(specKind = feedSpecKind), - noteCallbacks = NoteCallbacks(), + navigator = navigator, previewMode = true, pullToRefreshEnabled = false, pollingEnabled = false, diff --git a/app/src/main/kotlin/net/primal/android/feeds/list/FeedListBottomSheet.kt b/app/src/main/kotlin/net/primal/android/feeds/list/FeedListBottomSheet.kt index c9594b226..361fd946d 100644 --- a/app/src/main/kotlin/net/primal/android/feeds/list/FeedListBottomSheet.kt +++ b/app/src/main/kotlin/net/primal/android/feeds/list/FeedListBottomSheet.kt @@ -28,6 +28,7 @@ import net.primal.android.feeds.list.ui.DvmFeedDetails import net.primal.android.feeds.list.ui.DvmFeedMarketplace import net.primal.android.feeds.list.ui.FeedList import net.primal.android.feeds.list.ui.model.FeedUi +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.theme.AppTheme import net.primal.domain.feeds.FeedSpecKind import net.primal.domain.feeds.buildSpec @@ -41,6 +42,7 @@ fun FeedsBottomSheet( onGoToWallet: (() -> Unit)? = null, onDismissRequest: () -> Unit, sheetState: SheetState = rememberModalBottomSheetState(), + navigator: PrimalNavigator, ) { val viewModel = hiltViewModel( key = activeFeed.spec, @@ -55,6 +57,7 @@ fun FeedsBottomSheet( sheetState = sheetState, eventPublisher = viewModel::setEvent, onGoToWallet = onGoToWallet, + navigator = navigator, ) } @@ -67,6 +70,7 @@ private fun FeedsBottomSheet( sheetState: SheetState = rememberModalBottomSheetState(), eventPublisher: (FeedListContract.UiEvent) -> Unit, onGoToWallet: (() -> Unit)? = null, + navigator: PrimalNavigator, ) { ModalBottomSheet( modifier = Modifier.statusBarsPadding(), @@ -185,6 +189,7 @@ private fun FeedsBottomSheet( } } }, + navigator = navigator, ) } } diff --git a/app/src/main/kotlin/net/primal/android/feeds/list/ui/DvmFeedDetails.kt b/app/src/main/kotlin/net/primal/android/feeds/list/ui/DvmFeedDetails.kt index e9afc5436..a41467397 100644 --- a/app/src/main/kotlin/net/primal/android/feeds/list/ui/DvmFeedDetails.kt +++ b/app/src/main/kotlin/net/primal/android/feeds/list/ui/DvmFeedDetails.kt @@ -20,6 +20,7 @@ import net.primal.android.core.compose.icons.primaliconpack.ArrowBack import net.primal.android.feeds.dvm.ui.DvmFeedUi import net.primal.android.feeds.dvm.ui.DvmHeaderAndFeedList import net.primal.android.feeds.list.ui.model.FeedUi +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.theme.AppTheme @OptIn(ExperimentalMaterial3Api::class) @@ -31,6 +32,7 @@ fun DvmFeedDetails( onGoToWallet: (() -> Unit)? = null, modifier: Modifier = Modifier, onAddOrRemoveFeed: (() -> Unit)? = null, + navigator: PrimalNavigator, ) { Scaffold( modifier = modifier, @@ -56,6 +58,7 @@ fun DvmFeedDetails( modifier = Modifier.padding(paddingValues), dvmFeed = dvmFeed, onGoToWallet = onGoToWallet, + navigator = navigator, ) } }, diff --git a/app/src/main/kotlin/net/primal/android/messages/chat/ChatScreen.kt b/app/src/main/kotlin/net/primal/android/messages/chat/ChatScreen.kt index 5c1ff6d00..f90deacc6 100644 --- a/app/src/main/kotlin/net/primal/android/messages/chat/ChatScreen.kt +++ b/app/src/main/kotlin/net/primal/android/messages/chat/ChatScreen.kt @@ -1,5 +1,6 @@ package net.primal.android.messages.chat +import android.annotation.SuppressLint import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -70,9 +71,9 @@ import net.primal.android.core.compose.runtime.DisposableLifecycleObserverEffect import net.primal.android.core.ext.openUriSafely import net.primal.android.core.utils.formatNip05Identifier import net.primal.android.messages.chat.model.ChatMessageUi +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.model.toNoteContentUi import net.primal.android.notes.feed.note.ui.NoteContent -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.theme.AppTheme import net.primal.domain.nostr.utils.asEllipsizedNpub @@ -80,7 +81,7 @@ import net.primal.domain.nostr.utils.asEllipsizedNpub fun ChatScreen( viewModel: ChatViewModel, onClose: () -> Unit, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, ) { val state = viewModel.state.collectAsState() @@ -97,7 +98,7 @@ fun ChatScreen( ChatScreen( state = state.value, onClose = onClose, - noteCallbacks = noteCallbacks, + navigator = navigator, eventPublisher = { viewModel.setEvent(it) }, ) } @@ -107,7 +108,7 @@ fun ChatScreen( fun ChatScreen( state: ChatContract.UiState, onClose: () -> Unit, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, eventPublisher: (ChatContract.UiEvent) -> Unit, ) { val messagesPagingItems = state.messages.collectAsLazyPagingItems() @@ -144,7 +145,7 @@ fun ChatScreen( avatarCdnImage = state.participantProfile?.avatarCdnImage, avatarSize = 32.dp, avatarBlossoms = state.participantProfile?.profileBlossoms ?: emptyList(), - onClick = { noteCallbacks.onProfileClick?.invoke(state.participantId) }, + onClick = { navigator.onProfileClick(state.participantId) }, legendaryCustomization = state.participantProfile?.premiumDetails?.legendaryCustomization, ) } @@ -159,7 +160,7 @@ fun ChatScreen( state = listState, contentPadding = contentPadding, messages = messagesPagingItems, - noteCallbacks = noteCallbacks, + navigator = navigator, ) }, bottomBar = { @@ -193,7 +194,7 @@ fun ChatScreen( @Composable private fun ChatList( messages: LazyPagingItems, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, modifier: Modifier = Modifier, state: LazyListState = rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(0.dp), @@ -241,7 +242,7 @@ private fun ChatList( previousMessage = previousMessage, nextMessage = nextMessage, onUrlClick = { localUriHandler.openUriSafely(it) }, - noteCallbacks = noteCallbacks, + navigator = navigator, ) } @@ -292,13 +293,14 @@ private fun ChatList( } } +@SuppressLint("UnusedBoxWithConstraintsScope") @Composable private fun ChatMessageListItem( chatMessage: ChatMessageUi, previousMessage: ChatMessageUi? = null, nextMessage: ChatMessageUi? = null, onUrlClick: (String) -> Unit, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, ) { val timeDiffBetweenThisAndNextMessage = (nextMessage?.timestamp ?: Instant.MAX).epochSecond - chatMessage.timestamp.epochSecond @@ -372,7 +374,7 @@ private fun ChatMessageListItem( data = chatMessage.toNoteContentUi(), expanded = true, onClick = { }, - noteCallbacks = noteCallbacks, + navigator = navigator, onUrlClick = onUrlClick, contentColor = if (chatMessage.isUserMessage) { Color.White diff --git a/app/src/main/kotlin/net/primal/android/navigation/NavControllerExtensions.kt b/app/src/main/kotlin/net/primal/android/navigation/NavControllerExtensions.kt new file mode 100644 index 000000000..cbf5d3cd6 --- /dev/null +++ b/app/src/main/kotlin/net/primal/android/navigation/NavControllerExtensions.kt @@ -0,0 +1,196 @@ +package net.primal.android.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavOptions +import androidx.navigation.navOptions +import net.primal.android.editor.domain.NoteEditorArgs +import net.primal.android.explore.asearch.AdvancedSearchContract +import net.primal.android.explore.feed.ExploreFeedContract +import net.primal.android.explore.search.ui.SearchScope +import net.primal.android.profile.domain.ProfileFollowsType +import net.primal.core.utils.serialization.encodeToJsonString +import net.primal.domain.nostr.ReactionType + +internal fun NavController.navigateToWelcome() = + navigate( + route = "welcome", + navOptions = navOptions { clearBackStack() }, + ) + +internal fun NavController.navigateToLogin() = navigate(route = "login") + +internal fun NavController.navigateToOnboarding(promoCode: String? = null) = + navigate(route = "onboarding?$PROMO_CODE=$promoCode") + +internal fun NavController.navigateToWalletOnboarding(promoCode: String?) = + navigate(route = "onboardingWallet?$PROMO_CODE=$promoCode") + +internal fun NavController.navigateToLogout(profileId: String) = navigate(route = "logout?$PROFILE_ID=$profileId") + +internal fun NavController.navigateToSearch(searchScope: SearchScope) = + navigate(route = "search?$SEARCH_SCOPE=$searchScope") + +internal fun NavController.navigateToAdvancedSearch( + initialQuery: String? = null, + initialPostedBy: List? = null, + initialSearchKind: AdvancedSearchContract.SearchKind? = null, + initialSearchScope: AdvancedSearchContract.SearchScope? = null, +) = navigate( + route = "asearch" + + "?$INITIAL_QUERY=$initialQuery" + + "&$POSTED_BY=${initialPostedBy.encodeToJsonString()}" + + "&$SEARCH_KIND=$initialSearchKind" + + "&$ADV_SEARCH_SCOPE=$initialSearchScope", +) + +internal fun NavController.navigateToNoteEditor(args: NoteEditorArgs? = null) { + navigate(route = "noteEditor?$NOTE_EDITOR_ARGS=${args?.toJson()?.asBase64Encoded()}") +} + +internal val NavController.topLevelNavOptions: NavOptions + @SuppressWarnings("RestrictedApi") + get() { + val feedDestination = currentBackStack.value.find { + it.destination.route?.contains("home") == true + } + return navOptions { + popUpTo(id = feedDestination?.destination?.id ?: 0) + } + } + +internal fun NavController.navigateToHome() = + navigate( + route = "home", + navOptions = navOptions { clearBackStack() }, + ) + +internal fun NavController.navigateToReads() = + navigate( + route = "reads", + navOptions = topLevelNavOptions, + ) + +internal fun NavController.navigateToWallet() = + navigate( + route = "wallet", + navOptions = topLevelNavOptions, + ) + +internal fun NavController.navigateToNotifications() = + navigate( + route = "notifications", + navOptions = topLevelNavOptions, + ) + +internal fun NavController.navigateToExplore() = + navigate( + route = "explore", + navOptions = topLevelNavOptions, + ) + +internal fun NavController.navigateToFollowPack(profileId: String, followPackId: String) = + navigate(route = "explore/followPack/$profileId/$followPackId") + +internal fun NavController.navigateToRedeemCode(promoCode: String? = null) = + navigate(route = "redeemCode?$PROMO_CODE=$promoCode") + +internal fun NavController.navigateToMessages() = navigate(route = "messages") + +internal fun NavController.navigateToChat(profileId: String) = navigate(route = "messages/$profileId") + +internal fun NavController.navigateToNewMessage() = navigate(route = "messages/new") + +internal fun NavController.navigateToProfile(profileId: String) = navigate(route = "profile?$PROFILE_ID=$profileId") + +internal fun NavController.navigateToProfileQrCodeViewer(profileId: String? = null) = + when { + profileId != null -> navigate(route = "profileQrCodeViewer?$PROFILE_ID=$profileId") + else -> navigate(route = "profileQrCodeViewer") + } + +internal fun NavController.navigateToProfileFollows(profileId: String, followsType: ProfileFollowsType) = + navigate(route = "profile/$profileId/follows?$FOLLOWS_TYPE=$followsType") + +internal fun NavController.navigateToProfileEditor() = navigate(route = "profileEditor") + +internal fun NavController.navigateToSettings() = navigate(route = "settings") + +internal fun NavController.navigateToThread(noteId: String) = navigate(route = "thread/$noteId") + +internal fun NavController.navigateToArticleDetails(naddr: String) = navigate(route = "article?$NADDR=$naddr") + +internal fun NavController.navigateToReactions( + eventId: String, + initialTab: ReactionType = ReactionType.ZAPS, + articleATag: String?, +) = navigate("reactions/$eventId?$INITIAL_REACTION_TYPE=${initialTab.name}&$ARTICLE_A_TAG=$articleATag") + +internal fun NavController.navigateToMediaGallery( + noteId: String, + mediaUrl: String, + mediaPositionMs: Long = 0, +) = navigate( + route = "media/$noteId" + + "?$MEDIA_URL=$mediaUrl" + + "&$MEDIA_POSITION_MS=$mediaPositionMs", +) + +internal fun NavController.navigateToMediaItem(mediaUrl: String) { + val encodedUrl = mediaUrl.asUrlEncoded() + navigate(route = "mediaItem?$MEDIA_URL=$encodedUrl") +} + +internal fun NavController.navigateToExploreFeed( + feedSpec: String, + renderType: ExploreFeedContract.RenderType = ExploreFeedContract.RenderType.List, + feedTitle: String? = null, + feedDescription: String? = null, +) = navigate( + route = "explore/note?$EXPLORE_FEED_SPEC=${feedSpec.asBase64Encoded()}" + + "&$EXPLORE_FEED_TITLE=${feedTitle?.asBase64Encoded()}" + + "&$EXPLORE_FEED_DESCRIPTION=${feedDescription?.asBase64Encoded()}" + + "&$RENDER_TYPE=$renderType", +) + +internal fun NavController.navigateToBookmarks() = navigate(route = "bookmarks") + +internal fun NavController.navigateToPremiumBuying(fromOrigin: String? = null) { + if (fromOrigin?.isNotEmpty() == true) { + navigate(route = "premium/buying?$FROM_ORIGIN=$fromOrigin") + } else { + navigate(route = "premium/buying") + } +} + +internal fun NavController.navigateToUpgradeToPrimalPro() = + navigate(route = "premium/buying?$UPGRADE_TO_PRIMAL_PRO=true") + +internal fun NavController.navigateToPremiumExtendSubscription(primalName: String) = + navigate(route = "premium/buying?$EXTEND_EXISTING_PREMIUM_NAME=$primalName") + +internal fun NavController.navigateToPremiumHome() = navigate(route = "premium/home") +internal fun NavController.navigateToPremiumSupportPrimal() = navigate(route = "premium/supportPrimal") +internal fun NavController.navigateToLegendContributePrimal() = navigate(route = "premium/legend/contribution") +internal fun NavController.navigateToPremiumMoreInfo(tabIndex: Int = 0) = + navigate(route = "premium/info?$PREMIUM_MORE_INFO_TAB_INDEX=$tabIndex") + +internal fun NavController.navigateToPremiumBuyPrimalLegend(fromOrigin: String? = null) { + if (fromOrigin?.isNotEmpty() == true) { + navigate(route = "premium/legend/buy?$FROM_ORIGIN=$fromOrigin") + } else { + navigate(route = "premium/legend/buy") + } +} + +internal fun NavController.navigateToPremiumLegendaryProfile() = navigate(route = "premium/legend/profile") +internal fun NavController.navigateToPremiumCard(profileId: String) = navigate(route = "premium/card/$profileId") +internal fun NavController.navigateToPremiumLegendLeaderboard() = navigate(route = "premium/legend/leaderboard") +internal fun NavController.navigateToPremiumOGsLeaderboard() = navigate(route = "premium/ogs/leaderboard") + +internal fun NavController.navigateToPremiumManage() = navigate(route = "premium/manage") +internal fun NavController.navigateToPremiumMediaManagement() = navigate(route = "premium/manage/media") +internal fun NavController.navigateToPremiumContactList() = navigate(route = "premium/manage/contacts") +internal fun NavController.navigateToPremiumContentBackup() = navigate(route = "premium/manage/content") +internal fun NavController.navigateToPremiumChangePrimalName() = navigate(route = "premium/manage/changePrimalName") +internal fun NavController.navigateToPremiumOrderHistory() = navigate(route = "premium/manage/order") +internal fun NavController.navigateToPremiumRelay() = navigate(route = "premium/manage/relay") diff --git a/app/src/main/kotlin/net/primal/android/navigation/PrimalAppNavigation.kt b/app/src/main/kotlin/net/primal/android/navigation/PrimalAppNavigation.kt index b44d7021d..f7ae88b31 100644 --- a/app/src/main/kotlin/net/primal/android/navigation/PrimalAppNavigation.kt +++ b/app/src/main/kotlin/net/primal/android/navigation/PrimalAppNavigation.kt @@ -13,6 +13,7 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.background import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.window.DialogProperties import androidx.hilt.navigation.compose.hiltViewModel @@ -20,7 +21,6 @@ import androidx.navigation.NamedNavArgument import androidx.navigation.NavController import androidx.navigation.NavDeepLink import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -28,7 +28,6 @@ import androidx.navigation.compose.dialog import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import androidx.navigation.navDeepLink -import androidx.navigation.navOptions import net.primal.android.articles.reads.ReadsScreen import net.primal.android.articles.reads.ReadsScreenContract import net.primal.android.articles.reads.ReadsViewModel @@ -87,6 +86,7 @@ import net.primal.android.messages.conversation.MessageConversationListViewModel import net.primal.android.messages.conversation.MessageListScreen import net.primal.android.messages.conversation.create.NewConversationContract import net.primal.android.messages.conversation.create.NewConversationScreen +import net.primal.android.navigation.navigator.PrimalAppRouter import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.notes.home.HomeFeedContract import net.primal.android.notes.home.HomeFeedScreen @@ -169,197 +169,12 @@ import net.primal.android.thread.notes.ThreadScreen import net.primal.android.thread.notes.ThreadViewModel import net.primal.android.wallet.activation.WalletActivationContract import net.primal.android.wallet.activation.WalletActivationViewModel -import net.primal.core.utils.serialization.encodeToJsonString import net.primal.domain.feeds.buildAdvancedSearchNotesFeedSpec import net.primal.domain.feeds.buildAdvancedSearchNotificationsFeedSpec import net.primal.domain.feeds.buildAdvancedSearchReadsFeedSpec import net.primal.domain.feeds.buildReadsTopicFeedSpec import net.primal.domain.nostr.ReactionType -private fun NavController.navigateToWelcome() = - navigate( - route = "welcome", - navOptions = navOptions { clearBackStack() }, - ) - -private fun NavController.navigateToLogin() = navigate(route = "login") - -private fun NavController.navigateToOnboarding(promoCode: String? = null) = - navigate(route = "onboarding?$PROMO_CODE=$promoCode") - -private fun NavController.navigateToWalletOnboarding(promoCode: String?) = - navigate(route = "onboardingWallet?$PROMO_CODE=$promoCode") - -private fun NavController.navigateToLogout(profileId: String) = navigate(route = "logout?$PROFILE_ID=$profileId") - -private fun NavController.navigateToSearch(searchScope: SearchScope) = - navigate(route = "search?$SEARCH_SCOPE=$searchScope") - -private fun NavController.navigateToAdvancedSearch( - initialQuery: String? = null, - initialPostedBy: List? = null, - initialSearchKind: AdvancedSearchContract.SearchKind? = null, - initialSearchScope: AdvancedSearchContract.SearchScope? = null, -) = navigate( - route = "asearch" + - "?$INITIAL_QUERY=$initialQuery" + - "&$POSTED_BY=${initialPostedBy.encodeToJsonString()}" + - "&$SEARCH_KIND=$initialSearchKind" + - "&$ADV_SEARCH_SCOPE=$initialSearchScope", -) - -private fun NavController.navigateToNoteEditor(args: NoteEditorArgs? = null) { - navigate(route = "noteEditor?$NOTE_EDITOR_ARGS=${args?.toJson()?.asBase64Encoded()}") -} - -private val NavController.topLevelNavOptions: NavOptions - @SuppressWarnings("RestrictedApi") - get() { - val feedDestination = currentBackStack.value.find { - it.destination.route?.contains("home") == true - } - return navOptions { - popUpTo(id = feedDestination?.destination?.id ?: 0) - } - } - -fun NavController.navigateToHome() = - navigate( - route = "home", - navOptions = navOptions { clearBackStack() }, - ) - -fun NavController.navigateToReads() = - navigate( - route = "reads", - navOptions = topLevelNavOptions, - ) - -fun NavController.navigateToWallet() = - navigate( - route = "wallet", - navOptions = topLevelNavOptions, - ) - -private fun NavController.navigateToNotifications() = - navigate( - route = "notifications", - navOptions = topLevelNavOptions, - ) - -fun NavController.navigateToExplore() = - navigate( - route = "explore", - navOptions = topLevelNavOptions, - ) - -fun NavController.navigateToFollowPack(profileId: String, followPackId: String) = - navigate(route = "explore/followPack/$profileId/$followPackId") - -fun NavController.navigateToRedeemCode(promoCode: String? = null) = - navigate(route = "redeemCode?$PROMO_CODE=$promoCode") - -private fun NavController.navigateToMessages() = navigate(route = "messages") - -private fun NavController.navigateToChat(profileId: String) = navigate(route = "messages/$profileId") - -private fun NavController.navigateToNewMessage() = navigate(route = "messages/new") - -fun NavController.navigateToProfile(profileId: String) = navigate(route = "profile?$PROFILE_ID=$profileId") - -fun NavController.navigateToProfileQrCodeViewer(profileId: String? = null) = - when { - profileId != null -> navigate(route = "profileQrCodeViewer?$PROFILE_ID=$profileId") - else -> navigate(route = "profileQrCodeViewer") - } - -fun NavController.navigateToProfileFollows(profileId: String, followsType: ProfileFollowsType) = - navigate(route = "profile/$profileId/follows?$FOLLOWS_TYPE=$followsType") - -fun NavController.navigateToProfileEditor() = navigate(route = "profileEditor") - -private fun NavController.navigateToSettings() = navigate(route = "settings") - -fun NavController.navigateToThread(noteId: String) = navigate(route = "thread/$noteId") - -fun NavController.navigateToArticleDetails(naddr: String) = navigate(route = "article?$NADDR=$naddr") - -fun NavController.navigateToReactions( - eventId: String, - initialTab: ReactionType = ReactionType.ZAPS, - articleATag: String?, -) = navigate("reactions/$eventId?$INITIAL_REACTION_TYPE=${initialTab.name}&$ARTICLE_A_TAG=$articleATag") - -fun NavController.navigateToMediaGallery( - noteId: String, - mediaUrl: String, - mediaPositionMs: Long = 0, -) = navigate( - route = "media/$noteId" + - "?$MEDIA_URL=$mediaUrl" + - "&$MEDIA_POSITION_MS=$mediaPositionMs", -) - -fun NavController.navigateToMediaItem(mediaUrl: String) { - val encodedUrl = mediaUrl.asUrlEncoded() - navigate(route = "mediaItem?$MEDIA_URL=$encodedUrl") -} - -fun NavController.navigateToExploreFeed( - feedSpec: String, - renderType: ExploreFeedContract.RenderType = ExploreFeedContract.RenderType.List, - feedTitle: String? = null, - feedDescription: String? = null, -) = navigate( - route = "explore/note?$EXPLORE_FEED_SPEC=${feedSpec.asBase64Encoded()}" + - "&$EXPLORE_FEED_TITLE=${feedTitle?.asBase64Encoded()}" + - "&$EXPLORE_FEED_DESCRIPTION=${feedDescription?.asBase64Encoded()}" + - "&$RENDER_TYPE=$renderType", -) - -private fun NavController.navigateToBookmarks() = navigate(route = "bookmarks") - -fun NavController.navigateToPremiumBuying(fromOrigin: String? = null) { - if (fromOrigin?.isNotEmpty() == true) { - navigate(route = "premium/buying?$FROM_ORIGIN=$fromOrigin") - } else { - navigate(route = "premium/buying") - } -} - -private fun NavController.navigateToUpgradeToPrimalPro() = - navigate(route = "premium/buying?$UPGRADE_TO_PRIMAL_PRO=true") - -private fun NavController.navigateToPremiumExtendSubscription(primalName: String) = - navigate(route = "premium/buying?$EXTEND_EXISTING_PREMIUM_NAME=$primalName") - -private fun NavController.navigateToPremiumHome() = navigate(route = "premium/home") -private fun NavController.navigateToPremiumSupportPrimal() = navigate(route = "premium/supportPrimal") -private fun NavController.navigateToLegendContributePrimal() = navigate(route = "premium/legend/contribution") -private fun NavController.navigateToPremiumMoreInfo(tabIndex: Int = 0) = - navigate(route = "premium/info?$PREMIUM_MORE_INFO_TAB_INDEX=$tabIndex") - -private fun NavController.navigateToPremiumBuyPrimalLegend(fromOrigin: String? = null) { - if (fromOrigin?.isNotEmpty() == true) { - navigate(route = "premium/legend/buy?$FROM_ORIGIN=$fromOrigin") - } else { - navigate(route = "premium/legend/buy") - } -} - -private fun NavController.navigateToPremiumLegendaryProfile() = navigate(route = "premium/legend/profile") -private fun NavController.navigateToPremiumCard(profileId: String) = navigate(route = "premium/card/$profileId") -private fun NavController.navigateToPremiumLegendLeaderboard() = navigate(route = "premium/legend/leaderboard") -private fun NavController.navigateToPremiumOGsLeaderboard() = navigate(route = "premium/ogs/leaderboard") - -private fun NavController.navigateToPremiumManage() = navigate(route = "premium/manage") -private fun NavController.navigateToPremiumMediaManagement() = navigate(route = "premium/manage/media") -private fun NavController.navigateToPremiumContactList() = navigate(route = "premium/manage/contacts") -private fun NavController.navigateToPremiumContentBackup() = navigate(route = "premium/manage/content") -private fun NavController.navigateToPremiumChangePrimalName() = navigate(route = "premium/manage/changePrimalName") -private fun NavController.navigateToPremiumOrderHistory() = navigate(route = "premium/manage/order") -private fun NavController.navigateToPremiumRelay() = navigate(route = "premium/manage/relay") - fun accountSwitcherCallbacksHandler(navController: NavController) = AccountSwitcherCallbacks( onActiveAccountChanged = { navController.navigateToHome() }, @@ -438,6 +253,10 @@ fun noteCallbacksHandler(navController: NavController) = fun SharedTransitionScope.PrimalAppNavigation(startDestination: String) { val navController = rememberNavController() + val primalAppRouter = remember(navController) { + PrimalAppRouter(navController) + } + val topLevelDestinationHandler: (PrimalTopLevelDestination) -> Unit = { when (it) { PrimalTopLevelDestination.Home -> navController.popBackStack() @@ -546,11 +365,13 @@ fun SharedTransitionScope.PrimalAppNavigation(startDestination: String) { }, ), navController = navController, + primalAppRouter = primalAppRouter, onTopLevelDestinationChanged = topLevelDestinationHandler, onDrawerScreenClick = drawerDestinationHandler, ) explore( + primalAppRouter = primalAppRouter, route = "explore", navController = navController, onTopLevelDestinationChanged = topLevelDestinationHandler, @@ -585,6 +406,7 @@ fun SharedTransitionScope.PrimalAppNavigation(startDestination: String) { uriPattern = "https://primal.net/bookmarks" }, ), + primalAppRouter = primalAppRouter, ) exploreFeed( @@ -623,6 +445,7 @@ fun SharedTransitionScope.PrimalAppNavigation(startDestination: String) { }, ), navController = navController, + primalAppRouter = primalAppRouter, ) search( @@ -775,6 +598,7 @@ fun SharedTransitionScope.PrimalAppNavigation(startDestination: String) { }, ), navController = navController, + primalAppRouter = primalAppRouter, ) newMessage(route = "messages/new", navController = navController) @@ -792,6 +616,7 @@ fun SharedTransitionScope.PrimalAppNavigation(startDestination: String) { ) noteEditor( + primalAppRouter = primalAppRouter, route = "noteEditor?$NOTE_EDITOR_ARGS={$NOTE_EDITOR_ARGS}", arguments = listOf( navArgument(NOTE_EDITOR_ARGS) { @@ -1231,6 +1056,7 @@ private fun NavGraphBuilder.reads( route: String, deepLinks: List, navController: NavController, + primalAppRouter: PrimalAppRouter, onTopLevelDestinationChanged: (PrimalTopLevelDestination) -> Unit, onDrawerScreenClick: (DrawerScreenDestination) -> Unit, ) = composable( @@ -1257,6 +1083,7 @@ private fun NavGraphBuilder.reads( }, ) { navBackEntry -> val viewModel = hiltViewModel(navBackEntry) + ApplyEdgeToEdge() LockToOrientationPortrait() ReadsScreen( @@ -1267,9 +1094,10 @@ private fun NavGraphBuilder.reads( callbacks = ReadsScreenContract.ScreenCallbacks( onDrawerQrCodeClick = { navController.navigateToProfileQrCodeViewer() }, onSearchClick = { navController.navigateToSearch(searchScope = SearchScope.Reads) }, - onArticleClick = { naddr -> navController.navigateToArticleDetails(naddr) }, + onArticleClick = { navController.navigateToArticleDetails(it) }, onGetPremiumClick = { navController.navigateToPremiumBuying() }, ), + navigator = primalAppRouter, ) } @@ -1278,6 +1106,7 @@ private fun NavGraphBuilder.noteEditor( deepLinks: List, arguments: List, navController: NavController, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, deepLinks = deepLinks, @@ -1304,6 +1133,7 @@ private fun NavGraphBuilder.noteEditor( navController.navigateUp() }, ), + navigator = primalAppRouter, ) } @@ -1313,6 +1143,7 @@ private fun NavGraphBuilder.explore( navController: NavController, onTopLevelDestinationChanged: (PrimalTopLevelDestination) -> Unit, onDrawerScreenClick: (DrawerScreenDestination) -> Unit, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, deepLinks = deepLinks, @@ -1339,20 +1170,23 @@ private fun NavGraphBuilder.explore( val viewModel = hiltViewModel(it) ApplyEdgeToEdge() LockToOrientationPortrait() + ExploreHomeScreen( viewModel = viewModel, onTopLevelDestinationChanged = onTopLevelDestinationChanged, onDrawerScreenClick = onDrawerScreenClick, - noteCallbacks = noteCallbacksHandler(navController), accountSwitcherCallbacks = accountSwitcherCallbacksHandler(navController = navController), callbacks = ExploreHomeContract.ScreenCallbacks( onDrawerQrCodeClick = { navController.navigateToProfileQrCodeViewer() }, onSearchClick = { navController.navigateToSearch(searchScope = SearchScope.Notes) }, onAdvancedSearchClick = { navController.navigateToAdvancedSearch() }, - onFollowPackClick = { profileId, identifier -> navController.navigateToFollowPack(profileId, identifier) }, + onFollowPackClick = { profileId, identifier -> + navController.navigateToFollowPack(profileId, identifier) + }, onGoToWallet = { navController.navigateToWallet() }, - onNewPostClick = { navController.navigateToNoteEditor(null) }, + onNewPostClick = { navController.navigateToNoteEditor() }, ), + navigator = primalAppRouter, ) } @@ -1392,6 +1226,7 @@ private fun NavGraphBuilder.exploreFeed( deepLinks: List, arguments: List, navController: NavController, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, deepLinks = deepLinks, @@ -1406,7 +1241,7 @@ private fun NavGraphBuilder.exploreFeed( LockToOrientationPortrait() ExploreFeedScreen( viewModel = viewModel, - noteCallbacks = noteCallbacksHandler(navController), + navigator = primalAppRouter, callbacks = ExploreFeedContract.ScreenCallbacks( onClose = { navController.navigateUp() }, onGoToWallet = { navController.navigateToWallet() }, @@ -1936,6 +1771,7 @@ private fun NavGraphBuilder.bookmarks( route: String, deepLinks: List, navController: NavController, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, deepLinks = deepLinks, @@ -1948,7 +1784,7 @@ private fun NavGraphBuilder.bookmarks( ApplyEdgeToEdge() BookmarksScreen( viewModel = viewModel, - noteCallbacks = noteCallbacksHandler(navController), + navigator = primalAppRouter, callbacks = BookmarksContract.ScreenCallbacks( onClose = { navController.navigateUp() }, onGoToWallet = { navController.navigateToWallet() }, @@ -1959,6 +1795,7 @@ private fun NavGraphBuilder.chat( route: String, arguments: List, navController: NavController, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, arguments = arguments, @@ -1973,7 +1810,7 @@ private fun NavGraphBuilder.chat( ChatScreen( viewModel = viewModel, onClose = { navController.navigateUp() }, - noteCallbacks = noteCallbacksHandler(navController), + navigator = primalAppRouter, ) } diff --git a/app/src/main/kotlin/net/primal/android/navigation/interactions/ArticleInteractionCallbacks.kt b/app/src/main/kotlin/net/primal/android/navigation/interactions/ArticleInteractionCallbacks.kt index 92450051c..ecbe4cba2 100644 --- a/app/src/main/kotlin/net/primal/android/navigation/interactions/ArticleInteractionCallbacks.kt +++ b/app/src/main/kotlin/net/primal/android/navigation/interactions/ArticleInteractionCallbacks.kt @@ -1,9 +1,9 @@ package net.primal.android.navigation.interactions -data class ArticleInteractionCallbacks( - val onArticleClick: (naddr: String) -> Unit, - val onArticleReplyClick: (naddr: String) -> Unit, - val onArticleQuoteClick: (naddr: String) -> Unit, - val onHighlightReplyClick: (highlightNevent: String, articleNaddr: String) -> Unit, - val onHighlightQuoteClick: (highlightNevent: String, articleNaddr: String) -> Unit, -) +interface ArticleInteractionCallbacks { + fun onArticleClick(naddr: String) + fun onArticleReplyClick(naddr: String) + fun onArticleQuoteClick(naddr: String) + fun onHighlightReplyClick(highlightNevent: String, articleNaddr: String) + fun onHighlightQuoteClick(highlightNevent: String, articleNaddr: String) +} diff --git a/app/src/main/kotlin/net/primal/android/navigation/interactions/ContentInteractionCallbacks.kt b/app/src/main/kotlin/net/primal/android/navigation/interactions/ContentInteractionCallbacks.kt index 46ce0c354..e96cf7e17 100644 --- a/app/src/main/kotlin/net/primal/android/navigation/interactions/ContentInteractionCallbacks.kt +++ b/app/src/main/kotlin/net/primal/android/navigation/interactions/ContentInteractionCallbacks.kt @@ -1,6 +1,6 @@ package net.primal.android.navigation.interactions -data class ContentInteractionCallbacks( - val onProfileClick: (profileId: String) -> Unit, - val onHashtagClick: (hashtag: String) -> Unit, -) +interface ContentInteractionCallbacks { + fun onProfileClick(profileId: String) + fun onHashtagClick(hashtag: String) +} diff --git a/app/src/main/kotlin/net/primal/android/navigation/interactions/NoteInteractionCallbacks.kt b/app/src/main/kotlin/net/primal/android/navigation/interactions/NoteInteractionCallbacks.kt index c0c75f2e0..51184de87 100644 --- a/app/src/main/kotlin/net/primal/android/navigation/interactions/NoteInteractionCallbacks.kt +++ b/app/src/main/kotlin/net/primal/android/navigation/interactions/NoteInteractionCallbacks.kt @@ -4,11 +4,15 @@ import net.primal.android.notes.feed.note.ui.events.InvoicePayClickEvent import net.primal.android.notes.feed.note.ui.events.MediaClickEvent import net.primal.domain.nostr.ReactionType -data class NoteInteractionCallbacks( - val onNoteClick: (noteId: String) -> Unit, - val onNoteReplyClick: (noteNevent: String) -> Unit, - val onNoteQuoteClick: (noteNevent: String) -> Unit, - val onMediaClick: (event: MediaClickEvent) -> Unit, - val onEventReactionsClick: (eventId: String, initialTab: ReactionType, articleATag: String?) -> Unit, - val onPayInvoiceClick: (event: InvoicePayClickEvent) -> Unit, -) +interface NoteInteractionCallbacks { + fun onNoteClick(noteId: String) + fun onNoteReplyClick(noteNevent: String) + fun onNoteQuoteClick(noteNevent: String) + fun onMediaClick(event: MediaClickEvent) + fun onEventReactionsClick( + eventId: String, + initialTab: ReactionType, + articleATag: String?, + ) + fun onPayInvoiceClick(event: InvoicePayClickEvent) +} diff --git a/app/src/main/kotlin/net/primal/android/navigation/interactions/PrimalSubscriptionsInteractionCallbacks.kt b/app/src/main/kotlin/net/primal/android/navigation/interactions/PrimalSubscriptionsInteractionCallbacks.kt index 65314658d..8e73e18e9 100644 --- a/app/src/main/kotlin/net/primal/android/navigation/interactions/PrimalSubscriptionsInteractionCallbacks.kt +++ b/app/src/main/kotlin/net/primal/android/navigation/interactions/PrimalSubscriptionsInteractionCallbacks.kt @@ -1,6 +1,6 @@ package net.primal.android.navigation.interactions -data class PrimalSubscriptionsInteractionCallbacks( - val onGetPrimalPremiumClick: () -> Unit, - val onPrimalLegendsLeaderboardClick: () -> Unit, -) +interface PrimalSubscriptionsInteractionCallbacks { + fun onGetPrimalPremiumClick() + fun onPrimalLegendsLeaderboardClick() +} diff --git a/app/src/main/kotlin/net/primal/android/navigation/navigator/NoOpNavigator.kt b/app/src/main/kotlin/net/primal/android/navigation/navigator/NoOpNavigator.kt new file mode 100644 index 000000000..a8db4d661 --- /dev/null +++ b/app/src/main/kotlin/net/primal/android/navigation/navigator/NoOpNavigator.kt @@ -0,0 +1,27 @@ +package net.primal.android.navigation.navigator + +import net.primal.android.notes.feed.note.ui.events.InvoicePayClickEvent +import net.primal.android.notes.feed.note.ui.events.MediaClickEvent +import net.primal.domain.nostr.ReactionType + +object NoOpNavigator : PrimalNavigator { + override fun onNoteClick(noteId: String) = Unit + override fun onNoteReplyClick(noteNevent: String) = Unit + override fun onNoteQuoteClick(noteNevent: String) = Unit + override fun onMediaClick(event: MediaClickEvent) = Unit + override fun onEventReactionsClick( + eventId: String, + initialTab: ReactionType, + articleATag: String?, + ) = Unit + override fun onPayInvoiceClick(event: InvoicePayClickEvent) = Unit + override fun onArticleClick(naddr: String) = Unit + override fun onArticleReplyClick(naddr: String) = Unit + override fun onArticleQuoteClick(naddr: String) = Unit + override fun onHighlightReplyClick(highlightNevent: String, articleNaddr: String) = Unit + override fun onHighlightQuoteClick(highlightNevent: String, articleNaddr: String) = Unit + override fun onProfileClick(profileId: String) = Unit + override fun onHashtagClick(hashtag: String) = Unit + override fun onGetPrimalPremiumClick() = Unit + override fun onPrimalLegendsLeaderboardClick() = Unit +} diff --git a/app/src/main/kotlin/net/primal/android/navigation/navigator/PrimalAppRouter.kt b/app/src/main/kotlin/net/primal/android/navigation/navigator/PrimalAppRouter.kt new file mode 100644 index 000000000..7ef9709c7 --- /dev/null +++ b/app/src/main/kotlin/net/primal/android/navigation/navigator/PrimalAppRouter.kt @@ -0,0 +1,75 @@ +package net.primal.android.navigation.navigator + +import androidx.navigation.NavController +import net.primal.android.editor.domain.NoteEditorArgs +import net.primal.android.navigation.navigateToArticleDetails +import net.primal.android.navigation.navigateToExploreFeed +import net.primal.android.navigation.navigateToMediaGallery +import net.primal.android.navigation.navigateToNoteEditor +import net.primal.android.navigation.navigateToPremiumBuying +import net.primal.android.navigation.navigateToPremiumLegendLeaderboard +import net.primal.android.navigation.navigateToProfile +import net.primal.android.navigation.navigateToReactions +import net.primal.android.navigation.navigateToThread +import net.primal.android.navigation.navigateToWalletCreateTransaction +import net.primal.android.notes.feed.note.ui.events.InvoicePayClickEvent +import net.primal.android.notes.feed.note.ui.events.MediaClickEvent +import net.primal.domain.feeds.buildAdvancedSearchNotesFeedSpec +import net.primal.domain.nostr.ReactionType + +internal class PrimalAppRouter(private val navController: NavController) : PrimalNavigator { + // --- NoteInteractionCallbacks Implementation --- + override fun onNoteClick(noteId: String) = navController.navigateToThread(noteId) + override fun onNoteReplyClick(noteNevent: String) = + navController.navigateToNoteEditor(NoteEditorArgs(referencedNoteNevent = noteNevent)) + override fun onNoteQuoteClick(noteNevent: String) = + navController.navigateToNoteEditor(args = NoteEditorArgs(referencedNoteNevent = noteNevent, isQuoting = true)) + override fun onMediaClick(event: MediaClickEvent) = + navController.navigateToMediaGallery( + noteId = event.noteId, + mediaUrl = event.mediaUrl, + mediaPositionMs = event.positionMs, + ) + override fun onEventReactionsClick( + eventId: String, + initialTab: ReactionType, + articleATag: String?, + ) = navController.navigateToReactions(eventId, initialTab, articleATag) + override fun onPayInvoiceClick(event: InvoicePayClickEvent) = + navController.navigateToWalletCreateTransaction(lnbc = event.lnbc) + + // --- ArticleInteractionCallbacks Implementation --- + override fun onArticleClick(naddr: String) = navController.navigateToArticleDetails(naddr) + override fun onArticleReplyClick(naddr: String) = + navController.navigateToNoteEditor(NoteEditorArgs(referencedArticleNaddr = naddr)) + override fun onArticleQuoteClick(naddr: String) = + navController.navigateToNoteEditor(args = NoteEditorArgs(referencedArticleNaddr = naddr, isQuoting = true)) + + override fun onHighlightReplyClick(highlightNevent: String, articleNaddr: String) { + navController.navigateToNoteEditor( + args = NoteEditorArgs( + referencedHighlightNevent = highlightNevent, + referencedArticleNaddr = articleNaddr, + ), + ) + } + + override fun onHighlightQuoteClick(highlightNevent: String, articleNaddr: String) { + navController.navigateToNoteEditor( + args = NoteEditorArgs( + referencedArticleNaddr = articleNaddr, + referencedHighlightNevent = highlightNevent, + isQuoting = true, + ), + ) + } + + // --- ContentInteractionCallbacks Implementation --- + override fun onProfileClick(profileId: String) = navController.navigateToProfile(profileId) + override fun onHashtagClick(hashtag: String) = + navController.navigateToExploreFeed(feedSpec = buildAdvancedSearchNotesFeedSpec(query = hashtag)) + + // --- PrimalSubscriptionsInteractionCallbacks Implementation --- + override fun onGetPrimalPremiumClick() = navController.navigateToPremiumBuying() + override fun onPrimalLegendsLeaderboardClick() = navController.navigateToPremiumLegendLeaderboard() +} diff --git a/app/src/main/kotlin/net/primal/android/navigation/navigator/PrimalNavigator.kt b/app/src/main/kotlin/net/primal/android/navigation/navigator/PrimalNavigator.kt new file mode 100644 index 000000000..ed097db0e --- /dev/null +++ b/app/src/main/kotlin/net/primal/android/navigation/navigator/PrimalNavigator.kt @@ -0,0 +1,12 @@ +package net.primal.android.navigation.navigator + +import net.primal.android.navigation.interactions.ArticleInteractionCallbacks +import net.primal.android.navigation.interactions.ContentInteractionCallbacks +import net.primal.android.navigation.interactions.NoteInteractionCallbacks +import net.primal.android.navigation.interactions.PrimalSubscriptionsInteractionCallbacks + +interface PrimalNavigator : + NoteInteractionCallbacks, + ArticleInteractionCallbacks, + ContentInteractionCallbacks, + PrimalSubscriptionsInteractionCallbacks diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/list/NoteFeedLazyColumn.kt b/app/src/main/kotlin/net/primal/android/notes/feed/list/NoteFeedLazyColumn.kt index 557c6ddb7..f85334a40 100644 --- a/app/src/main/kotlin/net/primal/android/notes/feed/list/NoteFeedLazyColumn.kt +++ b/app/src/main/kotlin/net/primal/android/notes/feed/list/NoteFeedLazyColumn.kt @@ -35,9 +35,9 @@ import net.primal.android.core.compose.isNotEmpty import net.primal.android.core.compose.rememberFirstVisibleItemIndex import net.primal.android.core.compose.zaps.FeedNoteTopZapsSection import net.primal.android.core.errors.UiError +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.model.FeedPostUi import net.primal.android.notes.feed.note.FeedNoteCard -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.domain.nostr.ReactionType import timber.log.Timber @@ -51,7 +51,7 @@ fun NoteFeedLazyColumn( pagingItems: LazyPagingItems, listState: LazyListState, showPaywall: Boolean, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, onGoToWallet: () -> Unit, showTopZaps: Boolean = false, shouldShowLoadingState: Boolean = true, @@ -118,7 +118,7 @@ fun NoteFeedLazyColumn( nestingCutOffLimit = FEED_NESTED_NOTES_CUT_OFF_LIMIT, showReplyTo = showReplyTo, couldAutoPlay = index == firstVisibleIndex, - noteCallbacks = noteCallbacks, + navigator = navigator, onGoToWallet = onGoToWallet, onUiError = onUiError, contentFooter = { @@ -129,13 +129,8 @@ fun NoteFeedLazyColumn( .padding(horizontal = 8.dp) .padding(top = 4.dp, end = 2.dp), zaps = item.eventZaps, - onClick = if (noteCallbacks.onEventReactionsClick != null) { - { - noteCallbacks.onEventReactionsClick - .invoke(item.postId, ReactionType.ZAPS, null) - } - } else { - null + onClick = { + navigator.onEventReactionsClick(item.postId, ReactionType.ZAPS, null) }, ) } @@ -212,7 +207,7 @@ fun NoteFeedLazyColumn( item(contentType = "Paywall") { PremiumFeedPaywall( onClick = { - noteCallbacks.onGetPrimalPremiumClick?.invoke() + navigator.onGetPrimalPremiumClick() }, ) } diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/list/NoteFeedList.kt b/app/src/main/kotlin/net/primal/android/notes/feed/list/NoteFeedList.kt index 2c98762d3..b3c4ad487 100644 --- a/app/src/main/kotlin/net/primal/android/notes/feed/list/NoteFeedList.kt +++ b/app/src/main/kotlin/net/primal/android/notes/feed/list/NoteFeedList.kt @@ -62,16 +62,16 @@ import net.primal.android.core.compose.pulltorefresh.PrimalPullToRefreshBox import net.primal.android.core.compose.runtime.DisposableLifecycleObserverEffect import net.primal.android.core.errors.UiError import net.primal.android.drawer.FloatingNewDataHostTopPadding +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.list.NoteFeedContract.UiEvent import net.primal.android.notes.feed.model.FeedPostUi import net.primal.android.notes.feed.model.FeedPostsSyncStats -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.theme.AppTheme @Composable fun NoteFeedList( feedSpec: String, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, onGoToWallet: () -> Unit, contentPadding: PaddingValues = PaddingValues(0.dp), newNotesNoticeAlpha: Float = 1.00f, @@ -121,7 +121,7 @@ fun NoteFeedList( NoteFeedList( state = uiState.value, - noteCallbacks = noteCallbacks, + navigator = navigator, onGoToWallet = onGoToWallet, newNotesNoticeAlpha = newNotesNoticeAlpha, showTopZaps = showTopZaps, @@ -141,7 +141,7 @@ fun NoteFeedList( @Composable private fun NoteFeedList( state: NoteFeedContract.UiState, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, onGoToWallet: () -> Unit, newNotesNoticeAlpha: Float = 1.00f, showTopZaps: Boolean = false, @@ -197,7 +197,7 @@ private fun NoteFeedList( feedListState = listState, showPaywall = state.paywall, showTopZaps = showTopZaps, - noteCallbacks = noteCallbacks, + navigator = navigator, onGoToWallet = onGoToWallet, paddingValues = contentPadding, onScrolledToTop = { eventPublisher(UiEvent.FeedScrolledToTop) }, @@ -246,7 +246,7 @@ fun NoteFeedList( feedListState: LazyListState, pagingItems: LazyPagingItems, showPaywall: Boolean, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, onGoToWallet: () -> Unit, showTopZaps: Boolean = false, pullToRefreshEnabled: Boolean = true, @@ -317,7 +317,7 @@ fun NoteFeedList( pagingItems = pagingItems, listState = feedListState, showPaywall = showPaywall, - noteCallbacks = noteCallbacks, + navigator = navigator, onGoToWallet = onGoToWallet, showTopZaps = showTopZaps, noContentText = noContentText, diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/FeedNoteCard.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/FeedNoteCard.kt index f1e3d214e..c09886a38 100644 --- a/app/src/main/kotlin/net/primal/android/notes/feed/note/FeedNoteCard.kt +++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/FeedNoteCard.kt @@ -61,6 +61,8 @@ import net.primal.android.core.compose.preview.PrimalPreview import net.primal.android.core.compose.profile.approvals.ApproveBookmarkAlertDialog import net.primal.android.core.errors.UiError import net.primal.android.core.ext.openUriSafely +import net.primal.android.navigation.navigator.NoOpNavigator +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.NoteRepostOrQuoteBottomSheet import net.primal.android.notes.feed.model.EventStatsUi import net.primal.android.notes.feed.model.FeedPostAction @@ -74,7 +76,6 @@ import net.primal.android.notes.feed.note.ui.NoteContent import net.primal.android.notes.feed.note.ui.NoteDropdownMenuIcon import net.primal.android.notes.feed.note.ui.NoteSurfaceCard import net.primal.android.notes.feed.note.ui.RepostedNotice -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.notes.feed.zaps.UnableToZapBottomSheet import net.primal.android.notes.feed.zaps.ZapBottomSheet import net.primal.android.profile.report.ReportUserDialog @@ -104,7 +105,7 @@ fun FeedNoteCard( noteOptionsMenuEnabled: Boolean = true, showNoteStatCounts: Boolean = true, couldAutoPlay: Boolean = false, - noteCallbacks: NoteCallbacks = NoteCallbacks(), + navigator: PrimalNavigator, onNoteDeleted: (() -> Unit)? = null, onGoToWallet: (() -> Unit)? = null, onUiError: ((UiError) -> Unit)? = null, @@ -150,7 +151,7 @@ fun FeedNoteCard( showNoteStatCounts = showNoteStatCounts, noteOptionsMenuEnabled = noteOptionsMenuEnabled, couldAutoPlay = couldAutoPlay, - noteCallbacks = noteCallbacks, + navigator = navigator, onGoToWallet = onGoToWallet, contentFooter = contentFooter, ) @@ -179,7 +180,7 @@ private fun FeedNoteCard( noteOptionsMenuEnabled: Boolean = true, showNoteStatCounts: Boolean = true, couldAutoPlay: Boolean = false, - noteCallbacks: NoteCallbacks = NoteCallbacks(), + navigator: PrimalNavigator, onGoToWallet: (() -> Unit)? = null, contentFooter: @Composable () -> Unit = {}, ) { @@ -263,7 +264,7 @@ private fun FeedNoteCard( ) }, onPostQuoteClick = { - noteCallbacks.onNoteQuoteClick?.invoke( + navigator.onNoteQuoteClick( data.asNeventString(), ) }, @@ -299,10 +300,9 @@ private fun FeedNoteCard( .wrapContentHeight() .padding(cardPadding) .clickable( - enabled = noteCallbacks.onNoteClick != null, interactionSource = interactionSource, indication = ripple(), - onClick = { noteCallbacks.onNoteClick?.invoke(data.postId) }, + onClick = { navigator.onNoteClick(data.postId) }, ), shape = shape, colors = colors, @@ -362,8 +362,8 @@ private fun FeedNoteCard( .padding(horizontal = avatarPaddingDp) .padding(top = notePaddingDp * 2), repostedByAuthor = data.repostAuthorName, - onRepostAuthorClick = if (data.repostAuthorId != null && noteCallbacks.onProfileClick != null) { - { noteCallbacks.onProfileClick.invoke(data.repostAuthorId) } + onRepostAuthorClick = if (data.repostAuthorId != null) { + { navigator.onProfileClick(data.repostAuthorId) } } else { null }, @@ -404,11 +404,11 @@ private fun FeedNoteCard( textSelectable = textSelectable, showNoteStatCounts = showNoteStatCounts, couldAutoPlay = couldAutoPlay, - noteCallbacks = noteCallbacks, + navigator = navigator, onPostAction = { postAction -> when (postAction) { FeedPostAction.Reply -> { - noteCallbacks.onNoteReplyClick?.invoke( + navigator.onNoteReplyClick( data.asNeventString(), ) } @@ -529,7 +529,7 @@ private fun FeedNote( textSelectable: Boolean, showNoteStatCounts: Boolean, couldAutoPlay: Boolean, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, nestingCutOffLimit: Int = Int.MAX_VALUE, onPostAction: ((FeedPostAction) -> Unit)? = null, onPostLongClickAction: ((FeedPostAction) -> Unit)? = null, @@ -547,11 +547,7 @@ private fun FeedNote( avatarCdnImage = data.authorAvatarCdnImage, legendaryCustomization = data.authorLegendaryCustomization, avatarBlossoms = data.authorBlossoms, - onClick = if (noteCallbacks.onProfileClick != null) { - { noteCallbacks.onProfileClick.invoke(data.authorId) } - } else { - null - }, + onClick = { navigator.onProfileClick(data.authorId) }, ) } @@ -573,11 +569,7 @@ private fun FeedNote( authorLegendaryCustomization = data.authorLegendaryCustomization, authorBlossoms = data.authorBlossoms, replyToAuthor = if (showReplyTo) data.replyToAuthorHandle else null, - onAuthorAvatarClick = if (noteCallbacks.onProfileClick != null) { - { noteCallbacks.onProfileClick.invoke(data.authorId) } - } else { - null - }, + onAuthorAvatarClick = { navigator.onProfileClick(data.authorId) }, ) val postAuthorGuessHeight = with(LocalDensity.current) { 128.dp.toPx() } @@ -599,17 +591,13 @@ private fun FeedNote( enableTweetsMode = enableTweetsMode, textSelectable = textSelectable, nestingCutOffLimit = nestingCutOffLimit, - onClick = if (noteCallbacks.onNoteClick != null) { - { - launchRippleEffect(it) - noteCallbacks.onNoteClick.invoke(data.postId) - } - } else { - null + onClick = { + launchRippleEffect(it) + navigator.onNoteClick(data.postId) }, onUrlClick = { localUriHandler.openUriSafely(it) }, couldAutoPlay = couldAutoPlay, - noteCallbacks = noteCallbacks, + navigator = navigator, ) contentFooter() @@ -734,6 +722,7 @@ fun PreviewFeedNoteListItemLightMultiLineHeader( eventPublisher = {}, headerSingleLine = false, fullWidthContent = false, + navigator = NoOpNavigator, ) } } @@ -752,6 +741,7 @@ fun PreviewFeedNoteListItemLightMultiLineHeaderFullWidth( eventPublisher = {}, headerSingleLine = false, fullWidthContent = true, + navigator = NoOpNavigator, ) } } @@ -770,6 +760,7 @@ fun PreviewFeedNoteListItemDarkSingleLineHeader( eventPublisher = {}, headerSingleLine = true, fullWidthContent = false, + navigator = NoOpNavigator, ) } } @@ -788,6 +779,7 @@ fun PreviewFeedNoteListItemDarkSingleLineHeaderFullWidth( eventPublisher = {}, headerSingleLine = true, fullWidthContent = true, + navigator = NoOpNavigator, ) } } @@ -808,6 +800,7 @@ fun PreviewFeedNoteListItemLightForcedContentIndentFullWidthSingleLineHeader( fullWidthContent = true, forceContentIndent = true, drawLineBelowAvatar = true, + navigator = NoOpNavigator, ) } } @@ -828,6 +821,7 @@ fun PreviewFeedNoteListItemDarkForcedContentIndentSingleLineHeader( fullWidthContent = false, forceContentIndent = true, drawLineBelowAvatar = true, + navigator = NoOpNavigator, ) } } diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteContent.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteContent.kt index 48be71199..09657f270 100644 --- a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteContent.kt +++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/NoteContent.kt @@ -36,12 +36,13 @@ import net.primal.android.core.compose.zaps.ReferencedNoteZap import net.primal.android.core.compose.zaps.ReferencedZap import net.primal.android.core.utils.TextMatch import net.primal.android.core.utils.TextMatcher +import net.primal.android.navigation.navigator.NoOpNavigator +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.model.NoteContentUi import net.primal.android.notes.feed.model.NoteNostrUriUi import net.primal.android.notes.feed.model.asNoteNostrUriUi import net.primal.android.notes.feed.note.ui.attachment.NoteAttachments import net.primal.android.notes.feed.note.ui.events.InvoicePayClickEvent -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.premium.legend.domain.asLegendaryCustomization import net.primal.android.theme.AppTheme import net.primal.android.theme.domain.PrimalTheme @@ -160,7 +161,7 @@ fun NoteContent( modifier: Modifier = Modifier, data: NoteContentUi, expanded: Boolean, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, nestingLevel: Int = 0, nestingCutOffLimit: Int = Int.MAX_VALUE, maxLines: Int = Int.MAX_VALUE, @@ -219,17 +220,17 @@ fun NoteContent( ).firstOrNull() annotation?.handleAnnotationClick( - onProfileClick = noteCallbacks.onProfileClick, + onProfileClick = { navigator.onProfileClick(it) }, onUrlClick = { if (it.isPrimalLegendsUrl()) { - noteCallbacks.onPrimalLegendsLeaderboardClick?.invoke() + navigator.onPrimalLegendsLeaderboardClick() } else { onUrlClick?.invoke(it) } }, - onPostClick = noteCallbacks.onNoteClick, - onHashtagClick = noteCallbacks.onHashtagClick, - onArticleClick = noteCallbacks.onArticleClick, + onPostClick = { navigator.onNoteClick(it) }, + onHashtagClick = { navigator.onHashtagClick(it) }, + onArticleClick = { navigator.onArticleClick(it) }, ) ?: onClick?.invoke(offset) }, ) @@ -243,7 +244,7 @@ fun NoteContent( ReferencedHighlight( highlight = highlight, isDarkTheme = isDarkTheme, - onClick = { naddr -> noteCallbacks.onArticleClick?.invoke(naddr) }, + onClick = { naddr -> navigator.onArticleClick(naddr) }, ) if (index < referencedHighlights.size - 1) { @@ -256,7 +257,7 @@ fun NoteContent( NoteLightningInvoice( modifier = Modifier.padding(top = if (contentText.isEmpty()) 4.dp else 6.dp), invoice = data.invoices.first(), - onPayClick = { lnbc -> noteCallbacks.onPayInvoiceClick?.invoke(InvoicePayClickEvent(lnbc = lnbc)) }, + onPayClick = { lnbc -> navigator.onPayInvoiceClick(InvoicePayClickEvent(lnbc = lnbc)) }, ) } @@ -286,11 +287,11 @@ fun NoteContent( couldAutoPlay = couldAutoPlay, onUrlClick = { url -> when { - url.isPrimalLegendsUrl() -> noteCallbacks.onPrimalLegendsLeaderboardClick?.invoke() + url.isPrimalLegendsUrl() -> navigator.onPrimalLegendsLeaderboardClick() else -> onUrlClick?.invoke(url) } }, - onMediaClick = noteCallbacks.onMediaClick, + onMediaClick = { navigator.onMediaClick(it) }, ) } @@ -303,7 +304,7 @@ fun NoteContent( postResources = referencedPostResources, expanded = expanded, containerColor = referencedEventsContainerColor, - noteCallbacks = noteCallbacks, + navigator = navigator, hasBorder = referencedEventsHaveBorder, ) } @@ -315,7 +316,7 @@ fun NoteContent( articleResources = referencedArticleResources, expanded = expanded, containerColor = referencedEventsContainerColor, - noteCallbacks = noteCallbacks, + articleInteractionCallbacks = navigator, hasBorder = referencedEventsHaveBorder, ) } @@ -338,7 +339,7 @@ fun NoteContent( ), amountInSats = zap.amountInSats.toULong(), createdAt = Instant.ofEpochSecond(zap.createdAt), - noteCallbacks = noteCallbacks, + navigator = navigator, message = zap.message, senderAvatarCdnImage = zap.senderAvatarCdnImage, senderLegendaryCustomization = zap.senderPrimalLegendProfile?.asLegendaryCustomization(), @@ -357,7 +358,7 @@ fun NoteContent( receiverPrimalLegendProfile = zap.receiverPrimalLegendProfile, amountInSats = zap.amountInSats, message = zap.message, - noteCallbacks = noteCallbacks, + contentInteractionCallbacks = navigator, ) } } @@ -644,7 +645,7 @@ fun PreviewPostContent() { enableTweetsMode = false, onClick = {}, onUrlClick = {}, - noteCallbacks = NoteCallbacks(), + navigator = NoOpNavigator, ) } } @@ -679,7 +680,7 @@ fun PreviewPostUnknownReferencedEventWithAlt() { enableTweetsMode = false, onClick = {}, onUrlClick = {}, - noteCallbacks = NoteCallbacks(), + navigator = NoOpNavigator, ) } } @@ -714,7 +715,7 @@ fun PreviewPostUnknownReferencedEventWithoutAlt() { enableTweetsMode = false, onClick = {}, onUrlClick = {}, - noteCallbacks = NoteCallbacks(), + navigator = NoOpNavigator, ) } } @@ -802,7 +803,7 @@ fun PreviewPostContentWithReferencedPost() { enableTweetsMode = false, onClick = {}, onUrlClick = {}, - noteCallbacks = NoteCallbacks(), + navigator = NoOpNavigator, ) } } @@ -823,7 +824,7 @@ fun PreviewPostContentWithTweet() { enableTweetsMode = true, onClick = {}, onUrlClick = {}, - noteCallbacks = NoteCallbacks(), + navigator = NoOpNavigator, ) } } diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedArticlesColumn.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedArticlesColumn.kt index 18fd74f59..855ebada7 100644 --- a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedArticlesColumn.kt +++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedArticlesColumn.kt @@ -9,9 +9,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import net.primal.android.navigation.interactions.ArticleInteractionCallbacks import net.primal.android.nostr.mappers.asFeedArticleUi import net.primal.android.notes.feed.model.NoteNostrUriUi -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -19,7 +19,7 @@ fun ReferencedArticlesColumn( articleResources: List, expanded: Boolean, containerColor: Color, - noteCallbacks: NoteCallbacks, + articleInteractionCallbacks: ArticleInteractionCallbacks, modifier: Modifier = Modifier, hasBorder: Boolean = false, ) { @@ -41,7 +41,7 @@ fun ReferencedArticlesColumn( colors = CardDefaults.cardColors(containerColor = containerColor), hasBorder = hasBorder, onClick = { - noteCallbacks.onArticleClick?.invoke(data.naddr) + articleInteractionCallbacks.onArticleClick(data.naddr) }, ) } diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedNoteCard.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedNoteCard.kt index 1dfe3a260..a37e8264c 100644 --- a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedNoteCard.kt +++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedNoteCard.kt @@ -16,10 +16,11 @@ import java.time.Instant import java.time.temporal.ChronoUnit import kotlin.time.Duration.Companion.minutes import net.primal.android.core.compose.preview.PrimalPreview +import net.primal.android.navigation.navigator.NoOpNavigator +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.notes.feed.model.EventStatsUi import net.primal.android.notes.feed.model.FeedPostUi import net.primal.android.notes.feed.model.toNoteContentUi -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.theme.AppTheme import net.primal.android.theme.domain.PrimalTheme import net.primal.domain.links.CdnImage @@ -28,7 +29,7 @@ import net.primal.domain.nostr.NostrEventKind @Composable fun ReferencedNoteCard( data: FeedPostUi, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, modifier: Modifier = Modifier, nestingLevel: Int = 0, nestingCutOffLimit: Int = Int.MAX_VALUE, @@ -39,8 +40,8 @@ fun ReferencedNoteCard( modifier = modifier .wrapContentHeight() .clickable( - enabled = noteCallbacks.onNoteClick != null && data.rawKind == NostrEventKind.ShortTextNote.value, - onClick = { noteCallbacks.onNoteClick?.invoke(data.postId) }, + enabled = data.rawKind == NostrEventKind.ShortTextNote.value, + onClick = { navigator.onNoteClick(data.postId) }, ), colors = colors, border = if (hasBorder) { @@ -61,7 +62,7 @@ fun ReferencedNoteCard( authorLegendaryCustomization = data.authorLegendaryCustomization, authorInternetIdentifier = data.authorInternetIdentifier, authorBlossoms = data.authorBlossoms, - onAuthorAvatarClick = { noteCallbacks.onProfileClick?.invoke(data.authorId) }, + onAuthorAvatarClick = { navigator.onProfileClick(data.authorId) }, ) NoteContent( @@ -73,9 +74,9 @@ fun ReferencedNoteCard( referencedEventsHaveBorder = true, nestingLevel = nestingLevel + 1, nestingCutOffLimit = nestingCutOffLimit, - onClick = { noteCallbacks.onNoteClick?.invoke(data.postId) }, - onUrlClick = { _ -> noteCallbacks.onNoteClick?.invoke(data.postId) }, - noteCallbacks = noteCallbacks, + onClick = { navigator.onNoteClick(data.postId) }, + onUrlClick = { _ -> navigator.onNoteClick(data.postId) }, + navigator = navigator, ) Spacer(modifier = Modifier.height(12.dp)) @@ -124,7 +125,7 @@ fun PreviewReferencedPostListItemLight() { rawNostrEventJson = "", replyToAuthorHandle = "alex", ), - noteCallbacks = NoteCallbacks(), + navigator = NoOpNavigator, ) } } @@ -165,7 +166,7 @@ fun PreviewReferencedPostListItemDark() { rawNostrEventJson = "", replyToAuthorHandle = null, ), - noteCallbacks = NoteCallbacks(), + navigator = NoOpNavigator, ) } } diff --git a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedNotesColumn.kt b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedNotesColumn.kt index db430a96c..ed0f776e1 100644 --- a/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedNotesColumn.kt +++ b/app/src/main/kotlin/net/primal/android/notes/feed/note/ui/ReferencedNotesColumn.kt @@ -8,16 +8,16 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import net.primal.android.navigation.navigator.PrimalNavigator import net.primal.android.nostr.mappers.asFeedPostUi import net.primal.android.notes.feed.model.NoteNostrUriUi -import net.primal.android.notes.feed.note.ui.events.NoteCallbacks @Composable fun ReferencedNotesColumn( postResources: List, expanded: Boolean, containerColor: Color, - noteCallbacks: NoteCallbacks, + navigator: PrimalNavigator, nestingLevel: Int, nestingCutOffLimit: Int, modifier: Modifier = Modifier, @@ -42,7 +42,7 @@ fun ReferencedNotesColumn( nestingLevel = nestingLevel, nestingCutOffLimit = nestingCutOffLimit, colors = CardDefaults.cardColors(containerColor = containerColor), - noteCallbacks = noteCallbacks, + navigator = navigator, ) } } diff --git a/app/src/main/kotlin/net/primal/android/notes/home/HomeFeedScreen.kt b/app/src/main/kotlin/net/primal/android/notes/home/HomeFeedScreen.kt index 969404bc9..25d3acb15 100644 --- a/app/src/main/kotlin/net/primal/android/notes/home/HomeFeedScreen.kt +++ b/app/src/main/kotlin/net/primal/android/notes/home/HomeFeedScreen.kt @@ -52,12 +52,14 @@ import net.primal.android.core.compose.icons.PrimalIcons import net.primal.android.core.compose.icons.primaliconpack.AvatarDefault import net.primal.android.core.compose.icons.primaliconpack.Search import net.primal.android.core.compose.runtime.DisposableLifecycleObserverEffect +import net.primal.android.core.errors.UiError import net.primal.android.core.errors.resolveUiErrorMessage import net.primal.android.drawer.DrawerScreenDestination import net.primal.android.drawer.PrimalDrawerScaffold import net.primal.android.drawer.multiaccount.events.AccountSwitcherCallbacks import net.primal.android.feeds.list.FeedsBottomSheet import net.primal.android.feeds.list.ui.model.FeedUi +import net.primal.android.navigation.navigator.NoOpNavigator import net.primal.android.notes.feed.list.NoteFeedList import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.notes.home.HomeFeedContract.UiEvent @@ -192,14 +194,14 @@ fun HomeFeedScreen( val feedUi = state.feeds[index] NoteFeedList( feedSpec = feedUi.spec, + navigator = NoOpNavigator, pollingEnabled = pollingStates[feedUi] ?: false, - noteCallbacks = noteCallbacks, showTopZaps = true, newNotesNoticeAlpha = (1 - topAppBarState.collapsedFraction) * 1.0f, onGoToWallet = callbacks.onGoToWallet, contentPadding = paddingValues, shouldAnimateScrollToTop = shouldAnimateScrollToTop, - onUiError = { uiError -> + onUiError = { uiError: UiError -> uiScope.launch { snackbarHostState.showSnackbar( message = uiError.resolveUiErrorMessage(context), @@ -253,6 +255,7 @@ private fun NoteFeedTopAppBar( FeedsBottomSheet( activeFeed = activeFeed, feedSpecKind = FeedSpecKind.Notes, + navigator = NoOpNavigator, onFeedClick = { feed -> feedPickerVisible = false onFeedChanged(feed) diff --git a/app/src/main/kotlin/net/primal/android/notifications/list/ui/NotificationListItem.kt b/app/src/main/kotlin/net/primal/android/notifications/list/ui/NotificationListItem.kt index ac2dcb574..0bcc6e4a2 100644 --- a/app/src/main/kotlin/net/primal/android/notifications/list/ui/NotificationListItem.kt +++ b/app/src/main/kotlin/net/primal/android/notifications/list/ui/NotificationListItem.kt @@ -42,6 +42,7 @@ import net.primal.android.core.compose.preview.PrimalPreview import net.primal.android.core.ext.openUriSafely import net.primal.android.core.utils.isOnlyEmoji import net.primal.android.core.utils.shortened +import net.primal.android.navigation.navigator.NoOpNavigator import net.primal.android.notes.feed.model.EventStatsUi import net.primal.android.notes.feed.model.FeedPostAction import net.primal.android.notes.feed.model.FeedPostUi @@ -210,7 +211,7 @@ private fun NotificationContent( expanded = false, onClick = { noteCallbacks.onNoteClick?.invoke(actionPost.postId) }, onUrlClick = { localUriHandler.openUriSafely(it) }, - noteCallbacks = noteCallbacks, + navigator = NoOpNavigator, ) FeedNoteActionsRow( diff --git a/app/src/main/kotlin/net/primal/android/profile/details/ProfileDetailsScreen.kt b/app/src/main/kotlin/net/primal/android/profile/details/ProfileDetailsScreen.kt index 4968654d6..11440f8ec 100644 --- a/app/src/main/kotlin/net/primal/android/profile/details/ProfileDetailsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/profile/details/ProfileDetailsScreen.kt @@ -69,6 +69,7 @@ import net.primal.android.core.compose.pulltorefresh.PrimalPullToRefreshBox import net.primal.android.core.compose.runtime.DisposableLifecycleObserverEffect import net.primal.android.core.errors.UiError import net.primal.android.core.errors.resolveUiErrorMessage +import net.primal.android.navigation.navigator.NoOpNavigator import net.primal.android.notes.feed.grid.MediaFeedGrid import net.primal.android.notes.feed.list.NoteFeedList import net.primal.android.notes.feed.note.ui.events.NoteCallbacks @@ -443,7 +444,7 @@ private fun ProfileDetailsHorizontalPager( (pageIndex == NOTES_TAB_INDEX || pageIndex == REPLIES_TAB_INDEX) && state.profileId != null -> { NoteFeedList( feedSpec = state.profileFeedSpecs[pageIndex].buildSpec(profileId = state.profileId), - noteCallbacks = noteCallbacks, + navigator = NoOpNavigator, onGoToWallet = callbacks.onGoToWallet, pollingEnabled = pageIndex == NOTES_TAB_INDEX, pullToRefreshEnabled = false, diff --git a/app/src/main/kotlin/net/primal/android/settings/appearance/AppearanceSettingsScreen.kt b/app/src/main/kotlin/net/primal/android/settings/appearance/AppearanceSettingsScreen.kt index f0eef653a..0ae1b8356 100644 --- a/app/src/main/kotlin/net/primal/android/settings/appearance/AppearanceSettingsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/settings/appearance/AppearanceSettingsScreen.kt @@ -70,6 +70,7 @@ import net.primal.android.core.compose.icons.primaliconpack.ArrowBack import net.primal.android.core.compose.icons.primaliconpack.FontSize import net.primal.android.core.compose.preview.PrimalPreview import net.primal.android.core.compose.settings.SettingsItem +import net.primal.android.navigation.navigator.NoOpNavigator import net.primal.android.notes.feed.model.EventStatsUi import net.primal.android.notes.feed.model.FeedPostUi import net.primal.android.notes.feed.note.FeedNoteCard @@ -505,6 +506,7 @@ private fun NotePreviewSection(modifier: Modifier) { colors = CardDefaults.cardColors( containerColor = AppTheme.extraColorScheme.surfaceVariantAlt2, ), + navigator = NoOpNavigator, ) } } diff --git a/app/src/main/kotlin/net/primal/android/settings/muted/tabs/MuteThreads.kt b/app/src/main/kotlin/net/primal/android/settings/muted/tabs/MuteThreads.kt index 8268c2f65..4bc51fdda 100644 --- a/app/src/main/kotlin/net/primal/android/settings/muted/tabs/MuteThreads.kt +++ b/app/src/main/kotlin/net/primal/android/settings/muted/tabs/MuteThreads.kt @@ -3,6 +3,7 @@ package net.primal.android.settings.muted.tabs import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.ui.unit.dp +import net.primal.android.navigation.navigator.NoOpNavigator import net.primal.android.notes.feed.list.NoteFeedList import net.primal.android.notes.feed.note.ui.events.NoteCallbacks @@ -14,8 +15,8 @@ fun MuteThreads( paddingValues: PaddingValues = PaddingValues(all = 0.dp), ) { NoteFeedList( + navigator = NoOpNavigator, feedSpec = defaultMuteThreadsFeedSpec, - noteCallbacks = noteCallbacks, onGoToWallet = onGoToWallet, contentPadding = paddingValues, pollingEnabled = false, diff --git a/app/src/main/kotlin/net/primal/android/thread/articles/details/ArticleDetailsScreen.kt b/app/src/main/kotlin/net/primal/android/thread/articles/details/ArticleDetailsScreen.kt index fe8dc6201..417a5e7bc 100644 --- a/app/src/main/kotlin/net/primal/android/thread/articles/details/ArticleDetailsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/thread/articles/details/ArticleDetailsScreen.kt @@ -77,6 +77,7 @@ import net.primal.android.core.compose.zaps.ArticleTopZapsSection import net.primal.android.core.errors.UiError import net.primal.android.core.errors.resolveUiErrorMessage import net.primal.android.core.ext.openUriSafely +import net.primal.android.navigation.navigator.NoOpNavigator import net.primal.android.notes.feed.NoteRepostOrQuoteBottomSheet import net.primal.android.notes.feed.model.EventStatsUi import net.primal.android.notes.feed.model.FeedPostAction @@ -661,7 +662,7 @@ private fun ArticleContentWithComments( ReferencedNoteCard( modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), data = part.note, - noteCallbacks = noteCallbacks, + navigator = NoOpNavigator, ) } } @@ -836,7 +837,7 @@ private fun ArticleContentWithComments( cardPadding = PaddingValues(vertical = 4.dp), headerSingleLine = true, showReplyTo = false, - noteCallbacks = noteCallbacks, + navigator = NoOpNavigator, onGoToWallet = onGoToWallet, onUiError = onUiError, ) diff --git a/app/src/main/kotlin/net/primal/android/thread/notes/ThreadScreen.kt b/app/src/main/kotlin/net/primal/android/thread/notes/ThreadScreen.kt index 8be25400d..16c4d15ae 100644 --- a/app/src/main/kotlin/net/primal/android/thread/notes/ThreadScreen.kt +++ b/app/src/main/kotlin/net/primal/android/thread/notes/ThreadScreen.kt @@ -97,6 +97,7 @@ import net.primal.android.editor.di.noteEditorViewModel import net.primal.android.editor.domain.NoteEditorArgs import net.primal.android.editor.ui.NoteOutlinedTextField import net.primal.android.editor.ui.NoteTagUserLazyColumn +import net.primal.android.navigation.navigator.NoOpNavigator import net.primal.android.notes.feed.model.EventStatsUi import net.primal.android.notes.feed.model.FeedPostUi import net.primal.android.notes.feed.model.asNeventString @@ -409,13 +410,14 @@ private fun ThreadLazyColumn( drawLineBelowAvatar = isConnectedForward(index, state.highlightPostIndex), showReplyTo = false, showNoteStatCounts = index != state.highlightPostIndex, - noteCallbacks = noteCallbacks.copy( - onNoteClick = { noteId -> - if (state.highlightPostId != noteId) { - noteCallbacks.onNoteClick?.invoke(noteId) - } - }, - ), + navigator = NoOpNavigator, +// noteCallbacks = noteCallbacks.copy( +// onNoteClick = { noteId -> +// if (state.highlightPostId != noteId) { +// noteCallbacks.onNoteClick?.invoke(noteId) +// } +// }, +// ), onNoteDeleted = { if (!isReply) { onRootPostDeleted() diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt index 237931925..62dbaca0f 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt @@ -101,6 +101,7 @@ import net.primal.android.core.errors.resolveUiErrorMessage import net.primal.android.core.ext.openUriSafely import net.primal.android.core.utils.ellipsizeMiddle import net.primal.android.core.utils.formatToDefaultDateTimeFormat +import net.primal.android.navigation.navigator.NoOpNavigator import net.primal.android.notes.feed.model.FeedPostUi import net.primal.android.notes.feed.note.FeedNoteCard import net.primal.android.notes.feed.note.ui.FeedNoteHeader @@ -274,7 +275,8 @@ private fun NotePost( data = data, modifier = Modifier.padding(horizontal = 12.dp), colors = transactionCardColors(), - noteCallbacks = noteCallbacks, + navigator = NoOpNavigator, +// noteCallbacks = noteCallbacks, onUiError = { uiError -> uiScope.launch { snackbarHostState.showSnackbar(