Skip to content

Commit

Permalink
Implement contextual search (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
markocic authored Oct 21, 2024
1 parent 0017fb0 commit ab5e0fe
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.primal.android.explore.search.ui

enum class SearchScope {
Notes,
Reads,
MyNotifications,
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,18 @@ import net.primal.android.theme.AppTheme
@Composable
fun SearchScreen(
viewModel: SearchViewModel,
searchScope: SearchScope,
onClose: () -> Unit,
onAdvancedSearchClick: (query: String) -> Unit,
onProfileClick: (String) -> Unit,
onNoteClick: (String) -> Unit,
onNaddrClick: (String) -> Unit,
onSearchContent: (query: String) -> Unit,
onSearchContent: (scope: SearchScope, query: String) -> Unit,
) {
val uiState = viewModel.state.collectAsState()
SearchScreen(
state = uiState.value,
searchScope = searchScope,
eventPublisher = { viewModel.setEvent(it) },
onClose = onClose,
onAdvancedSearchClick = onAdvancedSearchClick,
Expand All @@ -74,13 +76,14 @@ fun SearchScreen(
@Composable
fun SearchScreen(
state: SearchContract.UiState,
searchScope: SearchScope,
eventPublisher: (SearchContract.UiEvent) -> Unit,
onClose: () -> Unit,
onAdvancedSearchClick: (query: String) -> Unit,
onProfileClick: (String) -> Unit,
onNoteClick: (String) -> Unit,
onNaddrClick: (String) -> Unit,
onSearchContent: (query: String) -> Unit,
onSearchContent: (scope: SearchScope, query: String) -> Unit,
) {
val keyboardController = LocalSoftwareKeyboardController.current
val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -123,6 +126,7 @@ fun SearchScreen(
stringResource(id = R.string.explore_enter_query)
},
clickable = state.searchQuery.isNotBlank(),
searchScope = searchScope,
onClick = {
keyboardController?.hide()
scope.launch {
Expand All @@ -137,7 +141,7 @@ fun SearchScreen(
}
profileId != null -> onProfileClick(profileId)
naddr != null -> onNaddrClick(naddr)
else -> onSearchContent(query)
else -> onSearchContent(searchScope, query)
}
}
},
Expand Down Expand Up @@ -203,6 +207,7 @@ fun SearchContentListItem(
hint: String,
clickable: Boolean,
onClick: () -> Unit,
searchScope: SearchScope,
) {
ListItem(
modifier = Modifier.clickable(
Expand All @@ -222,8 +227,13 @@ fun SearchContentListItem(
)
},
supportingContent = {
val resourceId = when (searchScope) {
SearchScope.Notes -> R.string.explore_search_notes
SearchScope.Reads -> R.string.explore_search_reads
SearchScope.MyNotifications -> R.string.explore_search_notifications
}
Text(
text = stringResource(id = R.string.explore_search_nostr).lowercase(),
text = stringResource(id = resourceId).lowercase(),
color = AppTheme.extraColorScheme.onSurfaceVariantAlt4,
)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ fun String.isReadsFeedSpec() = this.contains("\"kind\":\"reads\"") || this.conta

fun buildAdvancedSearchNotesFeedSpec(query: String) = """{"id":"advsearch","query":"kind:1 $query"}"""

fun buildSimpleSearchNotesFeedSpec(query: String) = """{"id":"search","kind":"notes","query":"$query"}"""
fun buildAdvancedSearchReadsFeedSpec(query: String) = """{"id":"advsearch","query":"kind:30023 $query"}"""

fun buildAdvancedSearchNotificationsFeedSpec(query: String) =
"""{"id":"advsearch","query":"kind:1 scope:mynotifications $query"}"""

fun buildReadsTopicFeedSpec(hashtag: String) = """{"kind":"reads","topic":"${hashtag.substring(startIndex = 1)}"}"""

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package net.primal.android.navigation

import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavBackStackEntry
import net.primal.android.core.serialization.json.NostrJson
import net.primal.android.explore.search.ui.SearchScope
import net.primal.android.wallet.domain.DraftTx
import net.primal.android.wallet.transactions.send.prepare.tabs.SendPaymentTab

Expand All @@ -28,6 +30,14 @@ inline val SavedStateHandle.renderType: String
const val INITIAL_QUERY = "initialQuery"
inline val SavedStateHandle.initialQuery: String? get() = get(INITIAL_QUERY)

const val SEARCH_SCOPE = "searchScope"
inline val SavedStateHandle.searchScopeOrThrow: SearchScope
get() = get<String>(SEARCH_SCOPE)?.let { SearchScope.valueOf(it) }
?: throw IllegalArgumentException("Missing required searchScope argument.")
inline val NavBackStackEntry.searchScopeOrThrow: SearchScope
get() = arguments?.getString(SEARCH_SCOPE)?.let { SearchScope.valueOf(it) }
?: throw IllegalArgumentException("Missing required searchScope argument.")

const val EXPLORE_FEED_SPEC = "exploreFeedSpec"
inline val SavedStateHandle.exploreFeedSpecOrThrow: String
get() = get<String>(EXPLORE_FEED_SPEC)?.asBase64Decoded()?.ifEmpty { null }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ import net.primal.android.explore.feed.ExploreFeedViewModel
import net.primal.android.explore.home.ExploreHomeScreen
import net.primal.android.explore.home.ExploreHomeViewModel
import net.primal.android.explore.search.SearchViewModel
import net.primal.android.explore.search.ui.SearchScope
import net.primal.android.explore.search.ui.SearchScreen
import net.primal.android.feeds.domain.buildAdvancedSearchNotesFeedSpec
import net.primal.android.feeds.domain.buildAdvancedSearchNotificationsFeedSpec
import net.primal.android.feeds.domain.buildAdvancedSearchReadsFeedSpec
import net.primal.android.feeds.domain.buildReadsTopicFeedSpec
import net.primal.android.feeds.domain.buildSimpleSearchNotesFeedSpec
import net.primal.android.messages.chat.ChatScreen
import net.primal.android.messages.chat.ChatViewModel
import net.primal.android.messages.conversation.MessageConversationListViewModel
Expand Down Expand Up @@ -109,7 +111,8 @@ private fun NavController.navigateToWalletOnboarding() = navigate(route = "onboa

private fun NavController.navigateToLogout() = navigate(route = "logout")

private fun NavController.navigateToSearch() = navigate(route = "search")
private fun NavController.navigateToSearch(searchScope: SearchScope) =
navigate(route = "search?$SEARCH_SCOPE=$searchScope")

private fun NavController.navigateToAdvancedSearch(initialQuery: String? = null) =
navigate(route = "asearch?$INITIAL_QUERY=$initialQuery")
Expand Down Expand Up @@ -359,7 +362,12 @@ fun PrimalAppNavigation() {
)

search(
route = "search",
route = "search?$SEARCH_SCOPE={$SEARCH_SCOPE}",
arguments = listOf(
navArgument(SEARCH_SCOPE) {
type = NavType.StringType
},
),
navController = navController,
)

Expand Down Expand Up @@ -656,7 +664,7 @@ private fun NavGraphBuilder.home(
onDrawerQrCodeClick = { navController.navigateToProfileQrCodeViewer() },
noteCallbacks = noteCallbacksHandler(navController),
onGoToWallet = { navController.navigateToWallet() },
onSearchClick = { navController.navigateToSearch() },
onSearchClick = { navController.navigateToSearch(searchScope = SearchScope.Notes) },
onNewPostClick = { preFillContent -> navController.navigateToNoteEditor(preFillContent?.asNoteEditorArgs()) },
)
}
Expand Down Expand Up @@ -696,7 +704,7 @@ private fun NavGraphBuilder.reads(
onTopLevelDestinationChanged = onTopLevelDestinationChanged,
onDrawerScreenClick = onDrawerScreenClick,
onDrawerQrCodeClick = { navController.navigateToProfileQrCodeViewer() },
onSearchClick = { navController.navigateToSearch() },
onSearchClick = { navController.navigateToSearch(searchScope = SearchScope.Reads) },
onArticleClick = { naddr -> navController.navigateToArticleDetails(naddr) },
)
}
Expand Down Expand Up @@ -757,7 +765,7 @@ private fun NavGraphBuilder.explore(
onTopLevelDestinationChanged = onTopLevelDestinationChanged,
onDrawerScreenClick = onDrawerScreenClick,
onDrawerQrCodeClick = { navController.navigateToProfileQrCodeViewer() },
onSearchClick = { navController.navigateToSearch() },
onSearchClick = { navController.navigateToSearch(searchScope = SearchScope.Notes) },
onAdvancedSearchClick = { navController.navigateToAdvancedSearch() },
onClose = { navController.navigateUp() },
noteCallbacks = noteCallbacksHandler(navController),
Expand Down Expand Up @@ -788,32 +796,42 @@ private fun NavGraphBuilder.exploreFeed(
)
}

private fun NavGraphBuilder.search(route: String, navController: NavController) =
composable(
route = route,
enterTransition = { primalSlideInHorizontallyFromEnd },
exitTransition = { primalScaleOut },
popEnterTransition = { primalScaleIn },
popExitTransition = { primalSlideOutHorizontallyToEnd },
) {
val viewModel = hiltViewModel<SearchViewModel>(it)
ApplyEdgeToEdge()
LockToOrientationPortrait()
SearchScreen(
viewModel = viewModel,
onClose = { navController.navigateUp() },
onAdvancedSearchClick = { query ->
navController.popBackStack()
navController.navigateToAdvancedSearch(initialQuery = query)
},
onProfileClick = { profileId -> navController.navigateToProfile(profileId) },
onNoteClick = { noteId -> navController.navigateToThread(noteId) },
onNaddrClick = { naddr -> navController.navigateToArticleDetails(naddr) },
onSearchContent = { query ->
navController.navigateToExploreFeed(feedSpec = buildSimpleSearchNotesFeedSpec(query = query))
},
)
}
private fun NavGraphBuilder.search(
route: String,
arguments: List<NamedNavArgument>,
navController: NavController,
) = composable(
route = route,
arguments = arguments,
enterTransition = { primalSlideInHorizontallyFromEnd },
exitTransition = { primalScaleOut },
popEnterTransition = { primalScaleIn },
popExitTransition = { primalSlideOutHorizontallyToEnd },
) {
val viewModel = hiltViewModel<SearchViewModel>(it)
ApplyEdgeToEdge()
LockToOrientationPortrait()
SearchScreen(
viewModel = viewModel,
searchScope = it.searchScopeOrThrow,
onClose = { navController.navigateUp() },
onAdvancedSearchClick = { query ->
navController.popBackStack()
navController.navigateToAdvancedSearch(initialQuery = query)
},
onProfileClick = { profileId -> navController.navigateToProfile(profileId) },
onNoteClick = { noteId -> navController.navigateToThread(noteId) },
onNaddrClick = { naddr -> navController.navigateToArticleDetails(naddr) },
onSearchContent = { scope, query ->
val feedSpec = when (scope) {
SearchScope.Notes -> buildAdvancedSearchNotesFeedSpec(query = query)
SearchScope.Reads -> buildAdvancedSearchReadsFeedSpec(query = query)
SearchScope.MyNotifications -> buildAdvancedSearchNotificationsFeedSpec(query = query)
}
navController.navigateToExploreFeed(feedSpec = feedSpec)
},
)
}

private fun NavGraphBuilder.advancedSearch(
route: String,
Expand Down Expand Up @@ -955,7 +973,7 @@ private fun NavGraphBuilder.notifications(
LockToOrientationPortrait()
NotificationsScreen(
viewModel = viewModel,
onSearchClick = { navController.navigateToSearch() },
onSearchClick = { navController.navigateToSearch(searchScope = SearchScope.MyNotifications) },
onGoToWallet = { navController.navigateToWallet() },
noteCallbacks = noteCallbacksHandler(navController),
onTopLevelDestinationChanged = onTopLevelDestinationChanged,
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@
<string name="note_reactions_title">Note Zaps</string>

<string name="explore_search_nostr">Search Nostr</string>
<string name="explore_search_notes">Search Notes</string>
<string name="explore_search_reads">Search Reads</string>
<string name="explore_search_notifications">Search Notifications</string>
<string name="explore_enter_query">Enter text to search</string>
<string name="explore_trending_topics_no_content">Trending topics will appear here.</string>
<string name="explore_feed_search_results">Search Results</string>
Expand Down

0 comments on commit ab5e0fe

Please sign in to comment.