From 7ef5b788aa62f22528b002fcb27604684ccaa399 Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Fri, 11 Jul 2025 00:12:13 +0200 Subject: [PATCH 1/2] Refactored the navigation graph to use a centralized NavigationManager provided via a static CompositionLocal --- .../android/navigation/NavigationManager.kt | 18 + .../android/navigation/PrimalAppNavigation.kt | 1055 +++++++++-------- 2 files changed, 589 insertions(+), 484 deletions(-) create mode 100644 app/src/main/kotlin/net/primal/android/navigation/NavigationManager.kt diff --git a/app/src/main/kotlin/net/primal/android/navigation/NavigationManager.kt b/app/src/main/kotlin/net/primal/android/navigation/NavigationManager.kt new file mode 100644 index 000000000..1c4021f17 --- /dev/null +++ b/app/src/main/kotlin/net/primal/android/navigation/NavigationManager.kt @@ -0,0 +1,18 @@ +package net.primal.android.navigation + +import androidx.compose.runtime.staticCompositionLocalOf +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 + +val LocalNavigationManager = staticCompositionLocalOf { + error("No NavigationManager provided. Make sure to provide it at the root of the navigation graph.") +} + +data class NavigationManager( + val noteCallbacks: NoteInteractionCallbacks, + val articleCallbacks: ArticleInteractionCallbacks, + val contentCallbacks: ContentInteractionCallbacks, + val subscriptionCallbacks: PrimalSubscriptionsInteractionCallbacks, +) 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..0c1794290 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,8 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.background import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.window.DialogProperties import androidx.hilt.navigation.compose.hiltViewModel @@ -87,6 +89,10 @@ 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.interactions.ArticleInteractionCallbacks +import net.primal.android.navigation.interactions.ContentInteractionCallbacks +import net.primal.android.navigation.interactions.NoteInteractionCallbacks +import net.primal.android.navigation.interactions.PrimalSubscriptionsInteractionCallbacks import net.primal.android.notes.feed.note.ui.events.NoteCallbacks import net.primal.android.notes.home.HomeFeedContract import net.primal.android.notes.home.HomeFeedScreen @@ -438,540 +444,621 @@ fun noteCallbacksHandler(navController: NavController) = fun SharedTransitionScope.PrimalAppNavigation(startDestination: String) { val navController = rememberNavController() - val topLevelDestinationHandler: (PrimalTopLevelDestination) -> Unit = { - when (it) { - PrimalTopLevelDestination.Home -> navController.popBackStack() - PrimalTopLevelDestination.Reads -> navController.navigateToReads() - PrimalTopLevelDestination.Wallet -> navController.navigateToWallet() - PrimalTopLevelDestination.Notifications -> navController.navigateToNotifications() - PrimalTopLevelDestination.Explore -> navController.navigateToExplore() - } - } + val navigationManager = remember(navController) { + val noteInteractions = NoteInteractionCallbacks( + onNoteClick = { noteId -> navController.navigateToThread(noteId = noteId) }, + onNoteReplyClick = { referencedNoteEvent -> + navController.navigateToNoteEditor(NoteEditorArgs(referencedNoteNevent = referencedNoteEvent)) + }, + onNoteQuoteClick = { noteNevent -> + navController.navigateToNoteEditor( + args = NoteEditorArgs(referencedNoteNevent = noteNevent, isQuoting = true), + ) + }, + onMediaClick = { + navController.navigateToMediaGallery( + noteId = it.noteId, + mediaUrl = it.mediaUrl, + mediaPositionMs = it.positionMs, + ) + }, + onEventReactionsClick = { eventId, initialTab, articleATag -> + navController.navigateToReactions( + eventId = eventId, + initialTab = initialTab, + articleATag = articleATag, + ) + }, + onPayInvoiceClick = { + navController.navigateToWalletCreateTransaction(lnbc = it.lnbc) + }, + ) - val drawerDestinationHandler: (DrawerScreenDestination) -> Unit = { - when (it) { - is DrawerScreenDestination.Profile -> navController.navigateToProfile(profileId = it.userId) - is DrawerScreenDestination.Premium -> if (it.hasPremium) { - navController.navigateToPremiumHome() - } else { - navController.navigateToPremiumBuying() - } + val articleInteractions = ArticleInteractionCallbacks( + onArticleClick = { naddr -> navController.navigateToArticleDetails(naddr = naddr) }, + onArticleReplyClick = { naddr -> + navController.navigateToNoteEditor(NoteEditorArgs(referencedArticleNaddr = naddr)) + }, + onArticleQuoteClick = { naddr -> + navController.navigateToNoteEditor( + args = NoteEditorArgs(referencedArticleNaddr = naddr, isQuoting = true), + ) + }, + onHighlightReplyClick = { highlightNevent, articleNaddr -> + navController.navigateToNoteEditor( + args = NoteEditorArgs( + referencedHighlightNevent = highlightNevent, + referencedArticleNaddr = articleNaddr, + ), + ) + }, + onHighlightQuoteClick = { nevent, naddr -> + navController.navigateToNoteEditor( + args = NoteEditorArgs( + referencedArticleNaddr = naddr, + referencedHighlightNevent = nevent, + isQuoting = true, + ), + ) + }, + ) - DrawerScreenDestination.Messages -> navController.navigateToMessages() - is DrawerScreenDestination.Bookmarks -> navController.navigateToBookmarks() - DrawerScreenDestination.RedeemCode -> navController.navigateToRedeemCode() - DrawerScreenDestination.Settings -> navController.navigateToSettings() - is DrawerScreenDestination.SignOut -> navController.navigateToLogout(profileId = it.userId) - } + val contentInteractions = ContentInteractionCallbacks( + onProfileClick = { profileId -> navController.navigateToProfile(profileId = profileId) }, + onHashtagClick = { hashtag -> + navController.navigateToExploreFeed(feedSpec = buildAdvancedSearchNotesFeedSpec(query = hashtag)) + }, + ) + + val subscriptionInteractions = PrimalSubscriptionsInteractionCallbacks( + onGetPrimalPremiumClick = { navController.navigateToPremiumBuying() }, + onPrimalLegendsLeaderboardClick = { navController.navigateToPremiumLegendLeaderboard() }, + ) + + NavigationManager( + noteCallbacks = noteInteractions, + articleCallbacks = articleInteractions, + contentCallbacks = contentInteractions, + subscriptionCallbacks = subscriptionInteractions, + ) } - NavHost( - modifier = Modifier.background(AppTheme.colorScheme.background), - navController = navController, - startDestination = startDestination, - ) { - welcome(route = "welcome", navController = navController) + CompositionLocalProvider(LocalNavigationManager provides navigationManager) { + val topLevelDestinationHandler: (PrimalTopLevelDestination) -> Unit = { + when (it) { + PrimalTopLevelDestination.Home -> navController.popBackStack() + PrimalTopLevelDestination.Reads -> navController.navigateToReads() + PrimalTopLevelDestination.Wallet -> navController.navigateToWallet() + PrimalTopLevelDestination.Notifications -> navController.navigateToNotifications() + PrimalTopLevelDestination.Explore -> navController.navigateToExplore() + } + } - login(route = "login", navController = navController) + val drawerDestinationHandler: (DrawerScreenDestination) -> Unit = { + when (it) { + is DrawerScreenDestination.Profile -> navController.navigateToProfile(profileId = it.userId) + is DrawerScreenDestination.Premium -> if (it.hasPremium) { + navController.navigateToPremiumHome() + } else { + navController.navigateToPremiumBuying() + } - onboarding( - route = "onboarding?$PROMO_CODE={$PROMO_CODE}", - arguments = listOf( - navArgument(PROMO_CODE) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + DrawerScreenDestination.Messages -> navController.navigateToMessages() + is DrawerScreenDestination.Bookmarks -> navController.navigateToBookmarks() + DrawerScreenDestination.RedeemCode -> navController.navigateToRedeemCode() + DrawerScreenDestination.Settings -> navController.navigateToSettings() + is DrawerScreenDestination.SignOut -> navController.navigateToLogout(profileId = it.userId) + } + } - onboardingWalletActivation( - route = "onboardingWallet?$PROMO_CODE={$PROMO_CODE}", - arguments = listOf( - navArgument(PROMO_CODE) { - type = NavType.StringType - nullable = true - }, - ), + NavHost( + modifier = Modifier.background(AppTheme.colorScheme.background), navController = navController, - ) + startDestination = startDestination, + ) { + welcome(route = "welcome", navController = navController) + + login(route = "login", navController = navController) + + onboarding( + route = "onboarding?$PROMO_CODE={$PROMO_CODE}", + arguments = listOf( + navArgument(PROMO_CODE) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - redeemCode( - route = "redeemCode?$PROMO_CODE={$PROMO_CODE}", - arguments = listOf( - navArgument(PROMO_CODE) { - type = NavType.StringType - nullable = true - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/rc/{$PROMO_CODE}" - }, - ), - navController = navController, - ) + onboardingWalletActivation( + route = "onboardingWallet?$PROMO_CODE={$PROMO_CODE}", + arguments = listOf( + navArgument(PROMO_CODE) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - logout( - route = "logout?$PROFILE_ID={$PROFILE_ID}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - nullable = false - }, - ), - navController = navController, - ) + redeemCode( + route = "redeemCode?$PROMO_CODE={$PROMO_CODE}", + arguments = listOf( + navArgument(PROMO_CODE) { + type = NavType.StringType + nullable = true + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/rc/{$PROMO_CODE}" + }, + ), + navController = navController, + ) - home( - route = "home", - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net" - }, - navDeepLink { - uriPattern = "https://primal.net/home" - }, - ), - ) + logout( + route = "logout?$PROFILE_ID={$PROFILE_ID}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + nullable = false + }, + ), + navController = navController, + ) - reads( - route = "reads", - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/reads" - }, - ), - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - ) + home( + route = "home", + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net" + }, + navDeepLink { + uriPattern = "https://primal.net/home" + }, + ), + ) - explore( - route = "explore", - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/explore" - }, - ), - ) + reads( + route = "reads", + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/reads" + }, + ), + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + ) - followPack( - route = "explore/followPack/{$PROFILE_ID}/{$FOLLOW_PACK_ID}", - navController = navController, - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - nullable = false - }, - navArgument(FOLLOW_PACK_ID) { - type = NavType.StringType - nullable = false - }, - ), - ) + explore( + route = "explore", + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/explore" + }, + ), + ) - bookmarks( - route = "bookmarks", - navController = navController, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/bookmarks" - }, - ), - ) + followPack( + route = "explore/followPack/{$PROFILE_ID}/{$FOLLOW_PACK_ID}", + navController = navController, + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + nullable = false + }, + navArgument(FOLLOW_PACK_ID) { + type = NavType.StringType + nullable = false + }, + ), + ) - exploreFeed( - route = "explore/note?" + - "$EXPLORE_FEED_SPEC={$EXPLORE_FEED_SPEC}&" + - "$ADVANCED_SEARCH_FEED_SPEC={$ADVANCED_SEARCH_FEED_SPEC}&" + - "$EXPLORE_FEED_TITLE={$EXPLORE_FEED_TITLE}&" + - "$EXPLORE_FEED_DESCRIPTION={$EXPLORE_FEED_DESCRIPTION}&" + - "$RENDER_TYPE={$RENDER_TYPE}", - arguments = listOf( - navArgument(EXPLORE_FEED_SPEC) { - type = NavType.StringType - nullable = true - }, - navArgument(ADVANCED_SEARCH_FEED_SPEC) { - type = NavType.StringType - nullable = true - }, - navArgument(EXPLORE_FEED_TITLE) { - type = NavType.StringType - nullable = true - }, - navArgument(EXPLORE_FEED_DESCRIPTION) { - type = NavType.StringType - nullable = true - }, - navArgument(RENDER_TYPE) { - type = NavType.StringType - nullable = false - defaultValue = ExploreFeedContract.RenderType.List.toString() - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/search/{$ADVANCED_SEARCH_FEED_SPEC}" - }, - ), - navController = navController, - ) + bookmarks( + route = "bookmarks", + navController = navController, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/bookmarks" + }, + ), + ) - search( - route = "search?$SEARCH_SCOPE={$SEARCH_SCOPE}", - arguments = listOf( - navArgument(SEARCH_SCOPE) { - type = NavType.StringType - }, - ), - navController = navController, - ) + exploreFeed( + route = "explore/note?" + + "$EXPLORE_FEED_SPEC={$EXPLORE_FEED_SPEC}&" + + "$ADVANCED_SEARCH_FEED_SPEC={$ADVANCED_SEARCH_FEED_SPEC}&" + + "$EXPLORE_FEED_TITLE={$EXPLORE_FEED_TITLE}&" + + "$EXPLORE_FEED_DESCRIPTION={$EXPLORE_FEED_DESCRIPTION}&" + + "$RENDER_TYPE={$RENDER_TYPE}", + arguments = listOf( + navArgument(EXPLORE_FEED_SPEC) { + type = NavType.StringType + nullable = true + }, + navArgument(ADVANCED_SEARCH_FEED_SPEC) { + type = NavType.StringType + nullable = true + }, + navArgument(EXPLORE_FEED_TITLE) { + type = NavType.StringType + nullable = true + }, + navArgument(EXPLORE_FEED_DESCRIPTION) { + type = NavType.StringType + nullable = true + }, + navArgument(RENDER_TYPE) { + type = NavType.StringType + nullable = false + defaultValue = ExploreFeedContract.RenderType.List.toString() + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/search/{$ADVANCED_SEARCH_FEED_SPEC}" + }, + ), + navController = navController, + ) - advancedSearch( - route = "asearch" + - "?$INITIAL_QUERY={$INITIAL_QUERY}" + - "&$POSTED_BY={$POSTED_BY}" + - "&$SEARCH_KIND={$SEARCH_KIND}" + - "&$ADV_SEARCH_SCOPE={$ADV_SEARCH_SCOPE}", - navController = navController, - arguments = listOf( - navArgument(INITIAL_QUERY) { - type = NavType.StringType - nullable = true - }, - navArgument(POSTED_BY) { - type = NavType.StringType - nullable = true - }, - navArgument(SEARCH_KIND) { - type = NavType.StringType - nullable = true - }, - navArgument(ADV_SEARCH_SCOPE) { - type = NavType.StringType - nullable = true - }, - ), - ) + search( + route = "search?$SEARCH_SCOPE={$SEARCH_SCOPE}", + arguments = listOf( + navArgument(SEARCH_SCOPE) { + type = NavType.StringType + }, + ), + navController = navController, + ) - premiumBuying( - route = "premium/buying" + - "?$EXTEND_EXISTING_PREMIUM_NAME={$EXTEND_EXISTING_PREMIUM_NAME}" + - "&$UPGRADE_TO_PRIMAL_PRO={$UPGRADE_TO_PRIMAL_PRO}" + - "&$FROM_ORIGIN={$FROM_ORIGIN}", - arguments = listOf( - navArgument(EXTEND_EXISTING_PREMIUM_NAME) { - type = NavType.StringType - nullable = true - }, - navArgument(UPGRADE_TO_PRIMAL_PRO) { - type = NavType.StringType - nullable = true - }, - navArgument(FROM_ORIGIN) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/premium" - }, - ), - ) + advancedSearch( + route = "asearch" + + "?$INITIAL_QUERY={$INITIAL_QUERY}" + + "&$POSTED_BY={$POSTED_BY}" + + "&$SEARCH_KIND={$SEARCH_KIND}" + + "&$ADV_SEARCH_SCOPE={$ADV_SEARCH_SCOPE}", + navController = navController, + arguments = listOf( + navArgument(INITIAL_QUERY) { + type = NavType.StringType + nullable = true + }, + navArgument(POSTED_BY) { + type = NavType.StringType + nullable = true + }, + navArgument(SEARCH_KIND) { + type = NavType.StringType + nullable = true + }, + navArgument(ADV_SEARCH_SCOPE) { + type = NavType.StringType + nullable = true + }, + ), + ) - premiumHome( - route = "premium/home", - navController = navController, - ) + premiumBuying( + route = "premium/buying" + + "?$EXTEND_EXISTING_PREMIUM_NAME={$EXTEND_EXISTING_PREMIUM_NAME}" + + "&$UPGRADE_TO_PRIMAL_PRO={$UPGRADE_TO_PRIMAL_PRO}" + + "&$FROM_ORIGIN={$FROM_ORIGIN}", + arguments = listOf( + navArgument(EXTEND_EXISTING_PREMIUM_NAME) { + type = NavType.StringType + nullable = true + }, + navArgument(UPGRADE_TO_PRIMAL_PRO) { + type = NavType.StringType + nullable = true + }, + navArgument(FROM_ORIGIN) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/premium" + }, + ), + ) - premiumSupportPrimal(route = "premium/supportPrimal", navController = navController) + premiumHome( + route = "premium/home", + navController = navController, + ) - premiumLegendContribution(route = "premium/legend/contribution", navController = navController) + premiumSupportPrimal(route = "premium/supportPrimal", navController = navController) - premiumMoreInfo( - route = "premium/info?$PREMIUM_MORE_INFO_TAB_INDEX={$PREMIUM_MORE_INFO_TAB_INDEX}", - arguments = listOf( - navArgument(PREMIUM_MORE_INFO_TAB_INDEX) { - type = NavType.IntType - defaultValue = 0 - }, - ), - navController = navController, - ) + premiumLegendContribution(route = "premium/legend/contribution", navController = navController) - premiumBuyPrimalLegend( - route = "premium/legend/buy?$FROM_ORIGIN={$FROM_ORIGIN}", - arguments = listOf( - navArgument(FROM_ORIGIN) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + premiumMoreInfo( + route = "premium/info?$PREMIUM_MORE_INFO_TAB_INDEX={$PREMIUM_MORE_INFO_TAB_INDEX}", + arguments = listOf( + navArgument(PREMIUM_MORE_INFO_TAB_INDEX) { + type = NavType.IntType + defaultValue = 0 + }, + ), + navController = navController, + ) - premiumLegendaryProfile(route = "premium/legend/profile", navController = navController) + premiumBuyPrimalLegend( + route = "premium/legend/buy?$FROM_ORIGIN={$FROM_ORIGIN}", + arguments = listOf( + navArgument(FROM_ORIGIN) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - premiumCard( - route = "premium/card/{$PROFILE_ID}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - }, - ), - navController = navController, - ) + premiumLegendaryProfile(route = "premium/legend/profile", navController = navController) - premiumLegendLeaderboard( - route = "premium/legend/leaderboard", - navController = navController, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/legends" - }, - ), - ) - premiumOGsLeaderboard(route = "premium/ogs/leaderboard", navController = navController) + premiumCard( + route = "premium/card/{$PROFILE_ID}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + }, + ), + navController = navController, + ) - premiumManage(route = "premium/manage", navController = navController) + premiumLegendLeaderboard( + route = "premium/legend/leaderboard", + navController = navController, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/legends" + }, + ), + ) + premiumOGsLeaderboard(route = "premium/ogs/leaderboard", navController = navController) - premiumContactList(route = "premium/manage/contacts", navController = navController) + premiumManage(route = "premium/manage", navController = navController) - premiumContentBackup(route = "premium/manage/content", navController = navController) + premiumContactList(route = "premium/manage/contacts", navController = navController) - premiumMediaManagement(route = "premium/manage/media", navController = navController) + premiumContentBackup(route = "premium/manage/content", navController = navController) - premiumChangePrimalName(route = "premium/manage/changePrimalName", navController = navController) + premiumMediaManagement(route = "premium/manage/media", navController = navController) - premiumOrderHistory(route = "premium/manage/order", navController = navController) + premiumChangePrimalName(route = "premium/manage/changePrimalName", navController = navController) - premiumRelay(route = "premium/manage/relay", navController = navController) + premiumOrderHistory(route = "premium/manage/order", navController = navController) - messages( - route = "messages", - navController = navController, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/dms" - }, - ), - ) + premiumRelay(route = "premium/manage/relay", navController = navController) - chat( - route = "messages/{$PROFILE_ID}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - }, - ), - navController = navController, - ) + messages( + route = "messages", + navController = navController, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/dms" + }, + ), + ) - newMessage(route = "messages/new", navController = navController) + chat( + route = "messages/{$PROFILE_ID}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + }, + ), + navController = navController, + ) - notifications( - route = "notifications", - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/notifications" - }, - ), - ) + newMessage(route = "messages/new", navController = navController) + + notifications( + route = "notifications", + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/notifications" + }, + ), + ) - noteEditor( - route = "noteEditor?$NOTE_EDITOR_ARGS={$NOTE_EDITOR_ARGS}", - arguments = listOf( - navArgument(NOTE_EDITOR_ARGS) { - type = NavType.StringType - nullable = true - defaultValue = null - }, - ), - deepLinks = listOf( - navDeepLink { - action = "ACTION_SEND" - mimeType = "image/*" - }, - navDeepLink { - action = "ACTION_SEND_MULTIPLE" - mimeType = "image/*" - }, - navDeepLink { - action = "ACTION_SEND" - mimeType = "video/*" - }, - navDeepLink { - action = "ACTION_SEND_MULTIPLE" - mimeType = "video/*" - }, - ), - navController = navController, - ) + noteEditor( + route = "noteEditor?$NOTE_EDITOR_ARGS={$NOTE_EDITOR_ARGS}", + arguments = listOf( + navArgument(NOTE_EDITOR_ARGS) { + type = NavType.StringType + nullable = true + defaultValue = null + }, + ), + deepLinks = listOf( + navDeepLink { + action = "ACTION_SEND" + mimeType = "image/*" + }, + navDeepLink { + action = "ACTION_SEND_MULTIPLE" + mimeType = "image/*" + }, + navDeepLink { + action = "ACTION_SEND" + mimeType = "video/*" + }, + navDeepLink { + action = "ACTION_SEND_MULTIPLE" + mimeType = "video/*" + }, + ), + navController = navController, + ) - thread( - route = "thread/{$NOTE_ID}", - arguments = listOf( - navArgument(NOTE_ID) { - type = NavType.StringType - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/e/{$NOTE_ID}" - }, - ), - navController = navController, - ) + thread( + route = "thread/{$NOTE_ID}", + arguments = listOf( + navArgument(NOTE_ID) { + type = NavType.StringType + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/e/{$NOTE_ID}" + }, + ), + navController = navController, + ) - articleDetails( - route = "article?$NADDR={$NADDR}&$PRIMAL_NAME={$PRIMAL_NAME}&$ARTICLE_ID={$ARTICLE_ID}", - arguments = listOf( - navArgument(NADDR) { - type = NavType.StringType - nullable = true - }, - navArgument(PRIMAL_NAME) { - type = NavType.StringType - nullable = true - }, - navArgument(ARTICLE_ID) { - type = NavType.StringType - nullable = true - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/a/{$NADDR}" - }, - navDeepLink { - uriPattern = "https://primal.net/{$PRIMAL_NAME}/{$ARTICLE_ID}" - }, - ), - navController = navController, - ) + articleDetails( + route = "article?$NADDR={$NADDR}&$PRIMAL_NAME={$PRIMAL_NAME}&$ARTICLE_ID={$ARTICLE_ID}", + arguments = listOf( + navArgument(NADDR) { + type = NavType.StringType + nullable = true + }, + navArgument(PRIMAL_NAME) { + type = NavType.StringType + nullable = true + }, + navArgument(ARTICLE_ID) { + type = NavType.StringType + nullable = true + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/a/{$NADDR}" + }, + navDeepLink { + uriPattern = "https://primal.net/{$PRIMAL_NAME}/{$ARTICLE_ID}" + }, + ), + navController = navController, + ) - reactions( - route = "reactions/{$EVENT_ID}" + - "?$INITIAL_REACTION_TYPE={$INITIAL_REACTION_TYPE}&$ARTICLE_A_TAG={$ARTICLE_A_TAG}", - arguments = listOf( - navArgument(EVENT_ID) { type = NavType.StringType }, - navArgument(INITIAL_REACTION_TYPE) { - type = NavType.StringType - defaultValue = ReactionType.ZAPS.name - }, - navArgument(ARTICLE_A_TAG) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + reactions( + route = "reactions/{$EVENT_ID}" + + "?$INITIAL_REACTION_TYPE={$INITIAL_REACTION_TYPE}&$ARTICLE_A_TAG={$ARTICLE_A_TAG}", + arguments = listOf( + navArgument(EVENT_ID) { type = NavType.StringType }, + navArgument(INITIAL_REACTION_TYPE) { + type = NavType.StringType + defaultValue = ReactionType.ZAPS.name + }, + navArgument(ARTICLE_A_TAG) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - media( - route = "media/{$NOTE_ID}" + - "?$MEDIA_URL={$MEDIA_URL}" + - "&$MEDIA_POSITION_MS={$MEDIA_POSITION_MS}", - arguments = listOf( - navArgument(NOTE_ID) { - type = NavType.StringType - }, - navArgument(MEDIA_URL) { - type = NavType.StringType - nullable = true - defaultValue = null - }, - navArgument(MEDIA_POSITION_MS) { - type = NavType.LongType - nullable = false - defaultValue = 0 - }, - ), - navController = navController, - ) + media( + route = "media/{$NOTE_ID}" + + "?$MEDIA_URL={$MEDIA_URL}" + + "&$MEDIA_POSITION_MS={$MEDIA_POSITION_MS}", + arguments = listOf( + navArgument(NOTE_ID) { + type = NavType.StringType + }, + navArgument(MEDIA_URL) { + type = NavType.StringType + nullable = true + defaultValue = null + }, + navArgument(MEDIA_POSITION_MS) { + type = NavType.LongType + nullable = false + defaultValue = 0 + }, + ), + navController = navController, + ) - mediaItem( - route = "mediaItem?$MEDIA_URL={$MEDIA_URL}", - arguments = listOf( - navArgument(MEDIA_URL) { - type = NavType.StringType - nullable = false - }, - ), - navController = navController, - ) + mediaItem( + route = "mediaItem?$MEDIA_URL={$MEDIA_URL}", + arguments = listOf( + navArgument(MEDIA_URL) { + type = NavType.StringType + nullable = false + }, + ), + navController = navController, + ) - profile( - route = "profile?$PROFILE_ID={$PROFILE_ID}&$PRIMAL_NAME={$PRIMAL_NAME}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - nullable = true - }, - navArgument(PRIMAL_NAME) { - type = NavType.StringType - nullable = true - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/p/{$PROFILE_ID}" - }, - navDeepLink { - uriPattern = "https://primal.net/{$PRIMAL_NAME}" - }, - ), - navController = navController, - ) + profile( + route = "profile?$PROFILE_ID={$PROFILE_ID}&$PRIMAL_NAME={$PRIMAL_NAME}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + nullable = true + }, + navArgument(PRIMAL_NAME) { + type = NavType.StringType + nullable = true + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/p/{$PROFILE_ID}" + }, + navDeepLink { + uriPattern = "https://primal.net/{$PRIMAL_NAME}" + }, + ), + navController = navController, + ) - profileFollows( - route = "profile/{$PROFILE_ID}/follows?$FOLLOWS_TYPE={$FOLLOWS_TYPE}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - }, - navArgument(FOLLOWS_TYPE) { - type = NavType.StringType - nullable = true - defaultValue = ProfileFollowsType.Following.name - }, - ), - navController = navController, - ) + profileFollows( + route = "profile/{$PROFILE_ID}/follows?$FOLLOWS_TYPE={$FOLLOWS_TYPE}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + }, + navArgument(FOLLOWS_TYPE) { + type = NavType.StringType + nullable = true + defaultValue = ProfileFollowsType.Following.name + }, + ), + navController = navController, + ) - profileEditor(route = "profileEditor", navController = navController) + profileEditor(route = "profileEditor", navController = navController) - profileQrCodeViewer( - route = "profileQrCodeViewer?$PROFILE_ID={$PROFILE_ID}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + profileQrCodeViewer( + route = "profileQrCodeViewer?$PROFILE_ID={$PROFILE_ID}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - settingsNavigation(route = "settings", navController = navController) + settingsNavigation(route = "settings", navController = navController) - walletNavigation( - route = "wallet", - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - ) + walletNavigation( + route = "wallet", + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + ) + } } } From 79b3340c4d28ec745a00f610262fe05026ec1559 Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Sun, 13 Jul 2025 19:40:48 +0200 Subject: [PATCH 2/2] Replace NoteCallbacks with PrimalNavigator and improve architecture --- .../android/articles/reads/ReadsScreen.kt | 15 +- .../articles/reads/ReadsScreenContract.kt | 2 +- .../android/bookmarks/list/BookmarksScreen.kt | 14 +- .../core/compose/zaps/ReferencedNoteZap.kt | 20 +- .../core/compose/zaps/ReferencedZap.kt | 28 +- .../primal/android/editor/NoteEditorScreen.kt | 34 +- .../android/explore/feed/ExploreFeedScreen.kt | 22 +- .../android/explore/home/ExploreHomeScreen.kt | 33 +- .../explore/home/feeds/ExploreFeeds.kt | 7 + .../android/explore/home/zaps/ExploreZaps.kt | 10 +- .../feeds/dvm/ui/DvmHeaderAndFeedList.kt | 5 +- .../android/feeds/list/FeedListBottomSheet.kt | 5 + .../android/feeds/list/ui/DvmFeedDetails.kt | 3 + .../android/messages/chat/ChatScreen.kt | 22 +- .../navigation/NavControllerExtensions.kt | 196 +++ .../android/navigation/NavigationManager.kt | 18 - .../android/navigation/PrimalAppNavigation.kt | 1276 +++++++---------- .../ArticleInteractionCallbacks.kt | 14 +- .../ContentInteractionCallbacks.kt | 8 +- .../interactions/NoteInteractionCallbacks.kt | 20 +- ...PrimalSubscriptionsInteractionCallbacks.kt | 8 +- .../navigation/navigator/NoOpNavigator.kt | 27 + .../navigation/navigator/PrimalAppRouter.kt | 75 + .../navigation/navigator/PrimalNavigator.kt | 12 + .../notes/feed/list/NoteFeedLazyColumn.kt | 17 +- .../android/notes/feed/list/NoteFeedList.kt | 14 +- .../android/notes/feed/note/FeedNoteCard.kt | 54 +- .../android/notes/feed/note/ui/NoteContent.kt | 41 +- .../feed/note/ui/ReferencedArticlesColumn.kt | 6 +- .../notes/feed/note/ui/ReferencedNoteCard.kt | 21 +- .../feed/note/ui/ReferencedNotesColumn.kt | 6 +- .../android/notes/home/HomeFeedScreen.kt | 7 +- .../list/ui/NotificationListItem.kt | 3 +- .../profile/details/ProfileDetailsScreen.kt | 3 +- .../appearance/AppearanceSettingsScreen.kt | 2 + .../settings/muted/tabs/MuteThreads.kt | 3 +- .../articles/details/ArticleDetailsScreen.kt | 5 +- .../android/thread/notes/ThreadScreen.kt | 16 +- .../details/TransactionDetailsScreen.kt | 4 +- 39 files changed, 1088 insertions(+), 988 deletions(-) create mode 100644 app/src/main/kotlin/net/primal/android/navigation/NavControllerExtensions.kt delete mode 100644 app/src/main/kotlin/net/primal/android/navigation/NavigationManager.kt create mode 100644 app/src/main/kotlin/net/primal/android/navigation/navigator/NoOpNavigator.kt create mode 100644 app/src/main/kotlin/net/primal/android/navigation/navigator/PrimalAppRouter.kt create mode 100644 app/src/main/kotlin/net/primal/android/navigation/navigator/PrimalNavigator.kt 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/NavigationManager.kt b/app/src/main/kotlin/net/primal/android/navigation/NavigationManager.kt deleted file mode 100644 index 1c4021f17..000000000 --- a/app/src/main/kotlin/net/primal/android/navigation/NavigationManager.kt +++ /dev/null @@ -1,18 +0,0 @@ -package net.primal.android.navigation - -import androidx.compose.runtime.staticCompositionLocalOf -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 - -val LocalNavigationManager = staticCompositionLocalOf { - error("No NavigationManager provided. Make sure to provide it at the root of the navigation graph.") -} - -data class NavigationManager( - val noteCallbacks: NoteInteractionCallbacks, - val articleCallbacks: ArticleInteractionCallbacks, - val contentCallbacks: ContentInteractionCallbacks, - val subscriptionCallbacks: PrimalSubscriptionsInteractionCallbacks, -) 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 0c1794290..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,7 +13,6 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideOutHorizontally import androidx.compose.foundation.background import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.window.DialogProperties @@ -22,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 @@ -30,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 @@ -89,10 +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.interactions.ArticleInteractionCallbacks -import net.primal.android.navigation.interactions.ContentInteractionCallbacks -import net.primal.android.navigation.interactions.NoteInteractionCallbacks -import net.primal.android.navigation.interactions.PrimalSubscriptionsInteractionCallbacks +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 @@ -175,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() }, @@ -444,621 +253,550 @@ fun noteCallbacksHandler(navController: NavController) = fun SharedTransitionScope.PrimalAppNavigation(startDestination: String) { val navController = rememberNavController() - val navigationManager = remember(navController) { - val noteInteractions = NoteInteractionCallbacks( - onNoteClick = { noteId -> navController.navigateToThread(noteId = noteId) }, - onNoteReplyClick = { referencedNoteEvent -> - navController.navigateToNoteEditor(NoteEditorArgs(referencedNoteNevent = referencedNoteEvent)) - }, - onNoteQuoteClick = { noteNevent -> - navController.navigateToNoteEditor( - args = NoteEditorArgs(referencedNoteNevent = noteNevent, isQuoting = true), - ) - }, - onMediaClick = { - navController.navigateToMediaGallery( - noteId = it.noteId, - mediaUrl = it.mediaUrl, - mediaPositionMs = it.positionMs, - ) - }, - onEventReactionsClick = { eventId, initialTab, articleATag -> - navController.navigateToReactions( - eventId = eventId, - initialTab = initialTab, - articleATag = articleATag, - ) - }, - onPayInvoiceClick = { - navController.navigateToWalletCreateTransaction(lnbc = it.lnbc) - }, - ) - - val articleInteractions = ArticleInteractionCallbacks( - onArticleClick = { naddr -> navController.navigateToArticleDetails(naddr = naddr) }, - onArticleReplyClick = { naddr -> - navController.navigateToNoteEditor(NoteEditorArgs(referencedArticleNaddr = naddr)) - }, - onArticleQuoteClick = { naddr -> - navController.navigateToNoteEditor( - args = NoteEditorArgs(referencedArticleNaddr = naddr, isQuoting = true), - ) - }, - onHighlightReplyClick = { highlightNevent, articleNaddr -> - navController.navigateToNoteEditor( - args = NoteEditorArgs( - referencedHighlightNevent = highlightNevent, - referencedArticleNaddr = articleNaddr, - ), - ) - }, - onHighlightQuoteClick = { nevent, naddr -> - navController.navigateToNoteEditor( - args = NoteEditorArgs( - referencedArticleNaddr = naddr, - referencedHighlightNevent = nevent, - isQuoting = true, - ), - ) - }, - ) - - val contentInteractions = ContentInteractionCallbacks( - onProfileClick = { profileId -> navController.navigateToProfile(profileId = profileId) }, - onHashtagClick = { hashtag -> - navController.navigateToExploreFeed(feedSpec = buildAdvancedSearchNotesFeedSpec(query = hashtag)) - }, - ) - - val subscriptionInteractions = PrimalSubscriptionsInteractionCallbacks( - onGetPrimalPremiumClick = { navController.navigateToPremiumBuying() }, - onPrimalLegendsLeaderboardClick = { navController.navigateToPremiumLegendLeaderboard() }, - ) + val primalAppRouter = remember(navController) { + PrimalAppRouter(navController) + } - NavigationManager( - noteCallbacks = noteInteractions, - articleCallbacks = articleInteractions, - contentCallbacks = contentInteractions, - subscriptionCallbacks = subscriptionInteractions, - ) + val topLevelDestinationHandler: (PrimalTopLevelDestination) -> Unit = { + when (it) { + PrimalTopLevelDestination.Home -> navController.popBackStack() + PrimalTopLevelDestination.Reads -> navController.navigateToReads() + PrimalTopLevelDestination.Wallet -> navController.navigateToWallet() + PrimalTopLevelDestination.Notifications -> navController.navigateToNotifications() + PrimalTopLevelDestination.Explore -> navController.navigateToExplore() + } } - CompositionLocalProvider(LocalNavigationManager provides navigationManager) { - val topLevelDestinationHandler: (PrimalTopLevelDestination) -> Unit = { - when (it) { - PrimalTopLevelDestination.Home -> navController.popBackStack() - PrimalTopLevelDestination.Reads -> navController.navigateToReads() - PrimalTopLevelDestination.Wallet -> navController.navigateToWallet() - PrimalTopLevelDestination.Notifications -> navController.navigateToNotifications() - PrimalTopLevelDestination.Explore -> navController.navigateToExplore() + val drawerDestinationHandler: (DrawerScreenDestination) -> Unit = { + when (it) { + is DrawerScreenDestination.Profile -> navController.navigateToProfile(profileId = it.userId) + is DrawerScreenDestination.Premium -> if (it.hasPremium) { + navController.navigateToPremiumHome() + } else { + navController.navigateToPremiumBuying() } + + DrawerScreenDestination.Messages -> navController.navigateToMessages() + is DrawerScreenDestination.Bookmarks -> navController.navigateToBookmarks() + DrawerScreenDestination.RedeemCode -> navController.navigateToRedeemCode() + DrawerScreenDestination.Settings -> navController.navigateToSettings() + is DrawerScreenDestination.SignOut -> navController.navigateToLogout(profileId = it.userId) } + } - val drawerDestinationHandler: (DrawerScreenDestination) -> Unit = { - when (it) { - is DrawerScreenDestination.Profile -> navController.navigateToProfile(profileId = it.userId) - is DrawerScreenDestination.Premium -> if (it.hasPremium) { - navController.navigateToPremiumHome() - } else { - navController.navigateToPremiumBuying() - } + NavHost( + modifier = Modifier.background(AppTheme.colorScheme.background), + navController = navController, + startDestination = startDestination, + ) { + welcome(route = "welcome", navController = navController) - DrawerScreenDestination.Messages -> navController.navigateToMessages() - is DrawerScreenDestination.Bookmarks -> navController.navigateToBookmarks() - DrawerScreenDestination.RedeemCode -> navController.navigateToRedeemCode() - DrawerScreenDestination.Settings -> navController.navigateToSettings() - is DrawerScreenDestination.SignOut -> navController.navigateToLogout(profileId = it.userId) - } - } + login(route = "login", navController = navController) - NavHost( - modifier = Modifier.background(AppTheme.colorScheme.background), + onboarding( + route = "onboarding?$PROMO_CODE={$PROMO_CODE}", + arguments = listOf( + navArgument(PROMO_CODE) { + type = NavType.StringType + nullable = true + }, + ), navController = navController, - startDestination = startDestination, - ) { - welcome(route = "welcome", navController = navController) - - login(route = "login", navController = navController) - - onboarding( - route = "onboarding?$PROMO_CODE={$PROMO_CODE}", - arguments = listOf( - navArgument(PROMO_CODE) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + ) - onboardingWalletActivation( - route = "onboardingWallet?$PROMO_CODE={$PROMO_CODE}", - arguments = listOf( - navArgument(PROMO_CODE) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + onboardingWalletActivation( + route = "onboardingWallet?$PROMO_CODE={$PROMO_CODE}", + arguments = listOf( + navArgument(PROMO_CODE) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - redeemCode( - route = "redeemCode?$PROMO_CODE={$PROMO_CODE}", - arguments = listOf( - navArgument(PROMO_CODE) { - type = NavType.StringType - nullable = true - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/rc/{$PROMO_CODE}" - }, - ), - navController = navController, - ) + redeemCode( + route = "redeemCode?$PROMO_CODE={$PROMO_CODE}", + arguments = listOf( + navArgument(PROMO_CODE) { + type = NavType.StringType + nullable = true + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/rc/{$PROMO_CODE}" + }, + ), + navController = navController, + ) - logout( - route = "logout?$PROFILE_ID={$PROFILE_ID}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - nullable = false - }, - ), - navController = navController, - ) + logout( + route = "logout?$PROFILE_ID={$PROFILE_ID}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + nullable = false + }, + ), + navController = navController, + ) - home( - route = "home", - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net" - }, - navDeepLink { - uriPattern = "https://primal.net/home" - }, - ), - ) + home( + route = "home", + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net" + }, + navDeepLink { + uriPattern = "https://primal.net/home" + }, + ), + ) - reads( - route = "reads", - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/reads" - }, - ), - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - ) + reads( + route = "reads", + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/reads" + }, + ), + navController = navController, + primalAppRouter = primalAppRouter, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + ) - explore( - route = "explore", - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/explore" - }, - ), - ) + explore( + primalAppRouter = primalAppRouter, + route = "explore", + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/explore" + }, + ), + ) - followPack( - route = "explore/followPack/{$PROFILE_ID}/{$FOLLOW_PACK_ID}", - navController = navController, - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - nullable = false - }, - navArgument(FOLLOW_PACK_ID) { - type = NavType.StringType - nullable = false - }, - ), - ) + followPack( + route = "explore/followPack/{$PROFILE_ID}/{$FOLLOW_PACK_ID}", + navController = navController, + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + nullable = false + }, + navArgument(FOLLOW_PACK_ID) { + type = NavType.StringType + nullable = false + }, + ), + ) - bookmarks( - route = "bookmarks", - navController = navController, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/bookmarks" - }, - ), - ) + bookmarks( + route = "bookmarks", + navController = navController, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/bookmarks" + }, + ), + primalAppRouter = primalAppRouter, + ) - exploreFeed( - route = "explore/note?" + - "$EXPLORE_FEED_SPEC={$EXPLORE_FEED_SPEC}&" + - "$ADVANCED_SEARCH_FEED_SPEC={$ADVANCED_SEARCH_FEED_SPEC}&" + - "$EXPLORE_FEED_TITLE={$EXPLORE_FEED_TITLE}&" + - "$EXPLORE_FEED_DESCRIPTION={$EXPLORE_FEED_DESCRIPTION}&" + - "$RENDER_TYPE={$RENDER_TYPE}", - arguments = listOf( - navArgument(EXPLORE_FEED_SPEC) { - type = NavType.StringType - nullable = true - }, - navArgument(ADVANCED_SEARCH_FEED_SPEC) { - type = NavType.StringType - nullable = true - }, - navArgument(EXPLORE_FEED_TITLE) { - type = NavType.StringType - nullable = true - }, - navArgument(EXPLORE_FEED_DESCRIPTION) { - type = NavType.StringType - nullable = true - }, - navArgument(RENDER_TYPE) { - type = NavType.StringType - nullable = false - defaultValue = ExploreFeedContract.RenderType.List.toString() - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/search/{$ADVANCED_SEARCH_FEED_SPEC}" - }, - ), - navController = navController, - ) + exploreFeed( + route = "explore/note?" + + "$EXPLORE_FEED_SPEC={$EXPLORE_FEED_SPEC}&" + + "$ADVANCED_SEARCH_FEED_SPEC={$ADVANCED_SEARCH_FEED_SPEC}&" + + "$EXPLORE_FEED_TITLE={$EXPLORE_FEED_TITLE}&" + + "$EXPLORE_FEED_DESCRIPTION={$EXPLORE_FEED_DESCRIPTION}&" + + "$RENDER_TYPE={$RENDER_TYPE}", + arguments = listOf( + navArgument(EXPLORE_FEED_SPEC) { + type = NavType.StringType + nullable = true + }, + navArgument(ADVANCED_SEARCH_FEED_SPEC) { + type = NavType.StringType + nullable = true + }, + navArgument(EXPLORE_FEED_TITLE) { + type = NavType.StringType + nullable = true + }, + navArgument(EXPLORE_FEED_DESCRIPTION) { + type = NavType.StringType + nullable = true + }, + navArgument(RENDER_TYPE) { + type = NavType.StringType + nullable = false + defaultValue = ExploreFeedContract.RenderType.List.toString() + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/search/{$ADVANCED_SEARCH_FEED_SPEC}" + }, + ), + navController = navController, + primalAppRouter = primalAppRouter, + ) - search( - route = "search?$SEARCH_SCOPE={$SEARCH_SCOPE}", - arguments = listOf( - navArgument(SEARCH_SCOPE) { - type = NavType.StringType - }, - ), - navController = navController, - ) + search( + route = "search?$SEARCH_SCOPE={$SEARCH_SCOPE}", + arguments = listOf( + navArgument(SEARCH_SCOPE) { + type = NavType.StringType + }, + ), + navController = navController, + ) - advancedSearch( - route = "asearch" + - "?$INITIAL_QUERY={$INITIAL_QUERY}" + - "&$POSTED_BY={$POSTED_BY}" + - "&$SEARCH_KIND={$SEARCH_KIND}" + - "&$ADV_SEARCH_SCOPE={$ADV_SEARCH_SCOPE}", - navController = navController, - arguments = listOf( - navArgument(INITIAL_QUERY) { - type = NavType.StringType - nullable = true - }, - navArgument(POSTED_BY) { - type = NavType.StringType - nullable = true - }, - navArgument(SEARCH_KIND) { - type = NavType.StringType - nullable = true - }, - navArgument(ADV_SEARCH_SCOPE) { - type = NavType.StringType - nullable = true - }, - ), - ) + advancedSearch( + route = "asearch" + + "?$INITIAL_QUERY={$INITIAL_QUERY}" + + "&$POSTED_BY={$POSTED_BY}" + + "&$SEARCH_KIND={$SEARCH_KIND}" + + "&$ADV_SEARCH_SCOPE={$ADV_SEARCH_SCOPE}", + navController = navController, + arguments = listOf( + navArgument(INITIAL_QUERY) { + type = NavType.StringType + nullable = true + }, + navArgument(POSTED_BY) { + type = NavType.StringType + nullable = true + }, + navArgument(SEARCH_KIND) { + type = NavType.StringType + nullable = true + }, + navArgument(ADV_SEARCH_SCOPE) { + type = NavType.StringType + nullable = true + }, + ), + ) - premiumBuying( - route = "premium/buying" + - "?$EXTEND_EXISTING_PREMIUM_NAME={$EXTEND_EXISTING_PREMIUM_NAME}" + - "&$UPGRADE_TO_PRIMAL_PRO={$UPGRADE_TO_PRIMAL_PRO}" + - "&$FROM_ORIGIN={$FROM_ORIGIN}", - arguments = listOf( - navArgument(EXTEND_EXISTING_PREMIUM_NAME) { - type = NavType.StringType - nullable = true - }, - navArgument(UPGRADE_TO_PRIMAL_PRO) { - type = NavType.StringType - nullable = true - }, - navArgument(FROM_ORIGIN) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/premium" - }, - ), - ) + premiumBuying( + route = "premium/buying" + + "?$EXTEND_EXISTING_PREMIUM_NAME={$EXTEND_EXISTING_PREMIUM_NAME}" + + "&$UPGRADE_TO_PRIMAL_PRO={$UPGRADE_TO_PRIMAL_PRO}" + + "&$FROM_ORIGIN={$FROM_ORIGIN}", + arguments = listOf( + navArgument(EXTEND_EXISTING_PREMIUM_NAME) { + type = NavType.StringType + nullable = true + }, + navArgument(UPGRADE_TO_PRIMAL_PRO) { + type = NavType.StringType + nullable = true + }, + navArgument(FROM_ORIGIN) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/premium" + }, + ), + ) - premiumHome( - route = "premium/home", - navController = navController, - ) + premiumHome( + route = "premium/home", + navController = navController, + ) - premiumSupportPrimal(route = "premium/supportPrimal", navController = navController) + premiumSupportPrimal(route = "premium/supportPrimal", navController = navController) - premiumLegendContribution(route = "premium/legend/contribution", navController = navController) + premiumLegendContribution(route = "premium/legend/contribution", navController = navController) - premiumMoreInfo( - route = "premium/info?$PREMIUM_MORE_INFO_TAB_INDEX={$PREMIUM_MORE_INFO_TAB_INDEX}", - arguments = listOf( - navArgument(PREMIUM_MORE_INFO_TAB_INDEX) { - type = NavType.IntType - defaultValue = 0 - }, - ), - navController = navController, - ) + premiumMoreInfo( + route = "premium/info?$PREMIUM_MORE_INFO_TAB_INDEX={$PREMIUM_MORE_INFO_TAB_INDEX}", + arguments = listOf( + navArgument(PREMIUM_MORE_INFO_TAB_INDEX) { + type = NavType.IntType + defaultValue = 0 + }, + ), + navController = navController, + ) - premiumBuyPrimalLegend( - route = "premium/legend/buy?$FROM_ORIGIN={$FROM_ORIGIN}", - arguments = listOf( - navArgument(FROM_ORIGIN) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + premiumBuyPrimalLegend( + route = "premium/legend/buy?$FROM_ORIGIN={$FROM_ORIGIN}", + arguments = listOf( + navArgument(FROM_ORIGIN) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - premiumLegendaryProfile(route = "premium/legend/profile", navController = navController) + premiumLegendaryProfile(route = "premium/legend/profile", navController = navController) - premiumCard( - route = "premium/card/{$PROFILE_ID}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - }, - ), - navController = navController, - ) + premiumCard( + route = "premium/card/{$PROFILE_ID}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + }, + ), + navController = navController, + ) - premiumLegendLeaderboard( - route = "premium/legend/leaderboard", - navController = navController, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/legends" - }, - ), - ) - premiumOGsLeaderboard(route = "premium/ogs/leaderboard", navController = navController) + premiumLegendLeaderboard( + route = "premium/legend/leaderboard", + navController = navController, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/legends" + }, + ), + ) + premiumOGsLeaderboard(route = "premium/ogs/leaderboard", navController = navController) - premiumManage(route = "premium/manage", navController = navController) + premiumManage(route = "premium/manage", navController = navController) - premiumContactList(route = "premium/manage/contacts", navController = navController) + premiumContactList(route = "premium/manage/contacts", navController = navController) - premiumContentBackup(route = "premium/manage/content", navController = navController) + premiumContentBackup(route = "premium/manage/content", navController = navController) - premiumMediaManagement(route = "premium/manage/media", navController = navController) + premiumMediaManagement(route = "premium/manage/media", navController = navController) - premiumChangePrimalName(route = "premium/manage/changePrimalName", navController = navController) + premiumChangePrimalName(route = "premium/manage/changePrimalName", navController = navController) - premiumOrderHistory(route = "premium/manage/order", navController = navController) + premiumOrderHistory(route = "premium/manage/order", navController = navController) - premiumRelay(route = "premium/manage/relay", navController = navController) + premiumRelay(route = "premium/manage/relay", navController = navController) - messages( - route = "messages", - navController = navController, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/dms" - }, - ), - ) + messages( + route = "messages", + navController = navController, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/dms" + }, + ), + ) - chat( - route = "messages/{$PROFILE_ID}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - }, - ), - navController = navController, - ) + chat( + route = "messages/{$PROFILE_ID}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + }, + ), + navController = navController, + primalAppRouter = primalAppRouter, + ) - newMessage(route = "messages/new", navController = navController) - - notifications( - route = "notifications", - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/notifications" - }, - ), - ) + newMessage(route = "messages/new", navController = navController) - noteEditor( - route = "noteEditor?$NOTE_EDITOR_ARGS={$NOTE_EDITOR_ARGS}", - arguments = listOf( - navArgument(NOTE_EDITOR_ARGS) { - type = NavType.StringType - nullable = true - defaultValue = null - }, - ), - deepLinks = listOf( - navDeepLink { - action = "ACTION_SEND" - mimeType = "image/*" - }, - navDeepLink { - action = "ACTION_SEND_MULTIPLE" - mimeType = "image/*" - }, - navDeepLink { - action = "ACTION_SEND" - mimeType = "video/*" - }, - navDeepLink { - action = "ACTION_SEND_MULTIPLE" - mimeType = "video/*" - }, - ), - navController = navController, - ) + notifications( + route = "notifications", + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/notifications" + }, + ), + ) - thread( - route = "thread/{$NOTE_ID}", - arguments = listOf( - navArgument(NOTE_ID) { - type = NavType.StringType - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/e/{$NOTE_ID}" - }, - ), - navController = navController, - ) + noteEditor( + primalAppRouter = primalAppRouter, + route = "noteEditor?$NOTE_EDITOR_ARGS={$NOTE_EDITOR_ARGS}", + arguments = listOf( + navArgument(NOTE_EDITOR_ARGS) { + type = NavType.StringType + nullable = true + defaultValue = null + }, + ), + deepLinks = listOf( + navDeepLink { + action = "ACTION_SEND" + mimeType = "image/*" + }, + navDeepLink { + action = "ACTION_SEND_MULTIPLE" + mimeType = "image/*" + }, + navDeepLink { + action = "ACTION_SEND" + mimeType = "video/*" + }, + navDeepLink { + action = "ACTION_SEND_MULTIPLE" + mimeType = "video/*" + }, + ), + navController = navController, + ) - articleDetails( - route = "article?$NADDR={$NADDR}&$PRIMAL_NAME={$PRIMAL_NAME}&$ARTICLE_ID={$ARTICLE_ID}", - arguments = listOf( - navArgument(NADDR) { - type = NavType.StringType - nullable = true - }, - navArgument(PRIMAL_NAME) { - type = NavType.StringType - nullable = true - }, - navArgument(ARTICLE_ID) { - type = NavType.StringType - nullable = true - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/a/{$NADDR}" - }, - navDeepLink { - uriPattern = "https://primal.net/{$PRIMAL_NAME}/{$ARTICLE_ID}" - }, - ), - navController = navController, - ) + thread( + route = "thread/{$NOTE_ID}", + arguments = listOf( + navArgument(NOTE_ID) { + type = NavType.StringType + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/e/{$NOTE_ID}" + }, + ), + navController = navController, + ) - reactions( - route = "reactions/{$EVENT_ID}" + - "?$INITIAL_REACTION_TYPE={$INITIAL_REACTION_TYPE}&$ARTICLE_A_TAG={$ARTICLE_A_TAG}", - arguments = listOf( - navArgument(EVENT_ID) { type = NavType.StringType }, - navArgument(INITIAL_REACTION_TYPE) { - type = NavType.StringType - defaultValue = ReactionType.ZAPS.name - }, - navArgument(ARTICLE_A_TAG) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + articleDetails( + route = "article?$NADDR={$NADDR}&$PRIMAL_NAME={$PRIMAL_NAME}&$ARTICLE_ID={$ARTICLE_ID}", + arguments = listOf( + navArgument(NADDR) { + type = NavType.StringType + nullable = true + }, + navArgument(PRIMAL_NAME) { + type = NavType.StringType + nullable = true + }, + navArgument(ARTICLE_ID) { + type = NavType.StringType + nullable = true + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/a/{$NADDR}" + }, + navDeepLink { + uriPattern = "https://primal.net/{$PRIMAL_NAME}/{$ARTICLE_ID}" + }, + ), + navController = navController, + ) - media( - route = "media/{$NOTE_ID}" + - "?$MEDIA_URL={$MEDIA_URL}" + - "&$MEDIA_POSITION_MS={$MEDIA_POSITION_MS}", - arguments = listOf( - navArgument(NOTE_ID) { - type = NavType.StringType - }, - navArgument(MEDIA_URL) { - type = NavType.StringType - nullable = true - defaultValue = null - }, - navArgument(MEDIA_POSITION_MS) { - type = NavType.LongType - nullable = false - defaultValue = 0 - }, - ), - navController = navController, - ) + reactions( + route = "reactions/{$EVENT_ID}" + + "?$INITIAL_REACTION_TYPE={$INITIAL_REACTION_TYPE}&$ARTICLE_A_TAG={$ARTICLE_A_TAG}", + arguments = listOf( + navArgument(EVENT_ID) { type = NavType.StringType }, + navArgument(INITIAL_REACTION_TYPE) { + type = NavType.StringType + defaultValue = ReactionType.ZAPS.name + }, + navArgument(ARTICLE_A_TAG) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - mediaItem( - route = "mediaItem?$MEDIA_URL={$MEDIA_URL}", - arguments = listOf( - navArgument(MEDIA_URL) { - type = NavType.StringType - nullable = false - }, - ), - navController = navController, - ) + media( + route = "media/{$NOTE_ID}" + + "?$MEDIA_URL={$MEDIA_URL}" + + "&$MEDIA_POSITION_MS={$MEDIA_POSITION_MS}", + arguments = listOf( + navArgument(NOTE_ID) { + type = NavType.StringType + }, + navArgument(MEDIA_URL) { + type = NavType.StringType + nullable = true + defaultValue = null + }, + navArgument(MEDIA_POSITION_MS) { + type = NavType.LongType + nullable = false + defaultValue = 0 + }, + ), + navController = navController, + ) - profile( - route = "profile?$PROFILE_ID={$PROFILE_ID}&$PRIMAL_NAME={$PRIMAL_NAME}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - nullable = true - }, - navArgument(PRIMAL_NAME) { - type = NavType.StringType - nullable = true - }, - ), - deepLinks = listOf( - navDeepLink { - uriPattern = "https://primal.net/p/{$PROFILE_ID}" - }, - navDeepLink { - uriPattern = "https://primal.net/{$PRIMAL_NAME}" - }, - ), - navController = navController, - ) + mediaItem( + route = "mediaItem?$MEDIA_URL={$MEDIA_URL}", + arguments = listOf( + navArgument(MEDIA_URL) { + type = NavType.StringType + nullable = false + }, + ), + navController = navController, + ) - profileFollows( - route = "profile/{$PROFILE_ID}/follows?$FOLLOWS_TYPE={$FOLLOWS_TYPE}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - }, - navArgument(FOLLOWS_TYPE) { - type = NavType.StringType - nullable = true - defaultValue = ProfileFollowsType.Following.name - }, - ), - navController = navController, - ) + profile( + route = "profile?$PROFILE_ID={$PROFILE_ID}&$PRIMAL_NAME={$PRIMAL_NAME}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + nullable = true + }, + navArgument(PRIMAL_NAME) { + type = NavType.StringType + nullable = true + }, + ), + deepLinks = listOf( + navDeepLink { + uriPattern = "https://primal.net/p/{$PROFILE_ID}" + }, + navDeepLink { + uriPattern = "https://primal.net/{$PRIMAL_NAME}" + }, + ), + navController = navController, + ) + + profileFollows( + route = "profile/{$PROFILE_ID}/follows?$FOLLOWS_TYPE={$FOLLOWS_TYPE}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + }, + navArgument(FOLLOWS_TYPE) { + type = NavType.StringType + nullable = true + defaultValue = ProfileFollowsType.Following.name + }, + ), + navController = navController, + ) - profileEditor(route = "profileEditor", navController = navController) + profileEditor(route = "profileEditor", navController = navController) - profileQrCodeViewer( - route = "profileQrCodeViewer?$PROFILE_ID={$PROFILE_ID}", - arguments = listOf( - navArgument(PROFILE_ID) { - type = NavType.StringType - nullable = true - }, - ), - navController = navController, - ) + profileQrCodeViewer( + route = "profileQrCodeViewer?$PROFILE_ID={$PROFILE_ID}", + arguments = listOf( + navArgument(PROFILE_ID) { + type = NavType.StringType + nullable = true + }, + ), + navController = navController, + ) - settingsNavigation(route = "settings", navController = navController) + settingsNavigation(route = "settings", navController = navController) - walletNavigation( - route = "wallet", - navController = navController, - onTopLevelDestinationChanged = topLevelDestinationHandler, - onDrawerScreenClick = drawerDestinationHandler, - ) - } + walletNavigation( + route = "wallet", + navController = navController, + onTopLevelDestinationChanged = topLevelDestinationHandler, + onDrawerScreenClick = drawerDestinationHandler, + ) } } @@ -1318,6 +1056,7 @@ private fun NavGraphBuilder.reads( route: String, deepLinks: List, navController: NavController, + primalAppRouter: PrimalAppRouter, onTopLevelDestinationChanged: (PrimalTopLevelDestination) -> Unit, onDrawerScreenClick: (DrawerScreenDestination) -> Unit, ) = composable( @@ -1344,6 +1083,7 @@ private fun NavGraphBuilder.reads( }, ) { navBackEntry -> val viewModel = hiltViewModel(navBackEntry) + ApplyEdgeToEdge() LockToOrientationPortrait() ReadsScreen( @@ -1354,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, ) } @@ -1365,6 +1106,7 @@ private fun NavGraphBuilder.noteEditor( deepLinks: List, arguments: List, navController: NavController, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, deepLinks = deepLinks, @@ -1391,6 +1133,7 @@ private fun NavGraphBuilder.noteEditor( navController.navigateUp() }, ), + navigator = primalAppRouter, ) } @@ -1400,6 +1143,7 @@ private fun NavGraphBuilder.explore( navController: NavController, onTopLevelDestinationChanged: (PrimalTopLevelDestination) -> Unit, onDrawerScreenClick: (DrawerScreenDestination) -> Unit, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, deepLinks = deepLinks, @@ -1426,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, ) } @@ -1479,6 +1226,7 @@ private fun NavGraphBuilder.exploreFeed( deepLinks: List, arguments: List, navController: NavController, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, deepLinks = deepLinks, @@ -1493,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() }, @@ -2023,6 +1771,7 @@ private fun NavGraphBuilder.bookmarks( route: String, deepLinks: List, navController: NavController, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, deepLinks = deepLinks, @@ -2035,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() }, @@ -2046,6 +1795,7 @@ private fun NavGraphBuilder.chat( route: String, arguments: List, navController: NavController, + primalAppRouter: PrimalAppRouter, ) = composable( route = route, arguments = arguments, @@ -2060,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(