Forces search types to be used by the search components and reorganizes search

This commit is contained in:
Vitor Pamplona
2023-05-02 11:39:29 -04:00
parent 80bfbf68b4
commit 89ce6dbab2
2 changed files with 74 additions and 40 deletions
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.decodePublicKey import com.vitorpamplona.amethyst.model.decodePublicKey
import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
import nostr.postr.bechToBytes import nostr.postr.bechToBytes
@@ -50,7 +51,7 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
) )
}, },
TypedFilter( TypedFilter(
types = COMMON_FEED_TYPES, types = setOf(FeedType.SEARCH),
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(MetadataEvent.kind), kinds = listOf(MetadataEvent.kind),
search = mySearchString, search = mySearchString,
@@ -58,9 +59,8 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
) )
), ),
TypedFilter( TypedFilter(
types = COMMON_FEED_TYPES, types = setOf(FeedType.SEARCH),
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, ChannelMetadataEvent.kind, ChannelCreateEvent.kind, ChannelMessageEvent.kind, HighlightEvent.kind),
search = mySearchString, search = mySearchString,
limit = 20 limit = 20
) )
@@ -46,6 +46,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
@@ -55,6 +57,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter
import com.vitorpamplona.amethyst.ui.note.ChannelName import com.vitorpamplona.amethyst.ui.note.ChannelName
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
@@ -127,14 +130,62 @@ fun SearchScreen(
} }
} }
class SearchBarViewModel : ViewModel() {
var searchValue by mutableStateOf("")
val searchResults = mutableStateOf<List<User>>(emptyList())
val searchResultsNotes = mutableStateOf<List<Note>>(emptyList())
val searchResultsChannels = mutableStateOf<List<Channel>>(emptyList())
val hashtagResults = mutableStateOf<List<String>>(emptyList())
val isTrailingIconVisible by
derivedStateOf {
searchValue.isNotBlank()
}
fun updateSearchValue(newValue: String) {
searchValue = newValue
}
private fun runSearch() {
if (searchValue.isBlank()) {
hashtagResults.value = emptyList()
searchResults.value = emptyList()
searchResultsChannels.value = emptyList()
searchResultsNotes.value = emptyList()
return
}
hashtagResults.value = findHashtags(searchValue)
searchResults.value = LocalCache.findUsersStartingWith(searchValue)
searchResultsNotes.value = LocalCache.findNotesStartingWith(searchValue).sortedBy { it.createdAt() }.reversed()
searchResultsChannels.value = LocalCache.findChannelsStartingWith(searchValue)
}
fun clean() {
searchValue = ""
searchResults.value = emptyList()
searchResultsChannels.value = emptyList()
searchResultsNotes.value = emptyList()
}
private val bundler = BundledUpdate(250, Dispatchers.IO) {
// adds the time to perform the refresh into this delay
// holding off new updates in case of heavy refresh routines.
runSearch()
}
fun invalidateData() {
bundler.invalidate()
}
fun isSearching() = searchValue.isNotBlank()
}
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
@Composable @Composable
private fun SearchBar(accountViewModel: AccountViewModel, navController: NavController) { private fun SearchBar(accountViewModel: AccountViewModel, navController: NavController) {
var searchValue by remember { mutableStateOf("") } val searchBarViewModel: SearchBarViewModel = viewModel()
val searchResults = remember { mutableStateOf<List<User>>(emptyList()) }
val searchResultsNotes = remember { mutableStateOf<List<Note>>(emptyList()) }
val searchResultsChannels = remember { mutableStateOf<List<Channel>>(emptyList()) }
val hashtagResults = remember { mutableStateOf<List<String>>(emptyList()) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -143,25 +194,16 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
val dbState = LocalCache.live.observeAsState() val dbState = LocalCache.live.observeAsState()
val db = dbState.value ?: return val db = dbState.value ?: return
val isTrailingIconVisible by remember {
derivedStateOf {
searchValue.isNotBlank()
}
}
// Create a channel for processing search queries. // Create a channel for processing search queries.
val searchTextChanges = remember { val searchTextChanges = remember {
CoroutineChannel<String>(CoroutineChannel.CONFLATED) CoroutineChannel<String>(CoroutineChannel.CONFLATED)
} }
LaunchedEffect(db) { LaunchedEffect(db) {
if (searchValue.length > 1) { withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { if (searchBarViewModel.isSearching()) {
searchResults.value = LocalCache.findUsersStartingWith(searchValue) println("Search Active")
searchResultsNotes.value = searchBarViewModel.invalidateData()
LocalCache.findNotesStartingWith(searchValue).sortedBy { it.createdAt() }
.reversed()
searchResultsChannels.value = LocalCache.findChannelsStartingWith(searchValue)
} }
} }
} }
@@ -174,15 +216,11 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
.distinctUntilChanged() .distinctUntilChanged()
.debounce(300) .debounce(300)
.collectLatest { .collectLatest {
hashtagResults.value = findHashtags(it) if (it.length >= 2) {
if (it.length >= 4) {
onlineSearch.search(it.trim()) onlineSearch.search(it.trim())
} }
searchResults.value = LocalCache.findUsersStartingWith(it) searchBarViewModel.invalidateData()
searchResultsNotes.value = LocalCache.findNotesStartingWith(it).sortedBy { it.createdAt() }.reversed()
searchResultsChannels.value = LocalCache.findChannelsStartingWith(it)
// makes sure to show the top of the search // makes sure to show the top of the search
scope.launch(Dispatchers.Main) { listState.animateScrollToItem(0) } scope.launch(Dispatchers.Main) { listState.animateScrollToItem(0) }
@@ -205,9 +243,9 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
TextField( TextField(
value = searchValue, value = searchBarViewModel.searchValue,
onValueChange = { onValueChange = {
searchValue = it searchBarViewModel.updateSearchValue(it)
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
searchTextChanges.trySend(it) searchTextChanges.trySend(it)
} }
@@ -234,14 +272,10 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
) )
}, },
trailingIcon = { trailingIcon = {
if (isTrailingIconVisible) { if (searchBarViewModel.isTrailingIconVisible) {
IconButton( IconButton(
onClick = { onClick = {
searchValue = "" searchBarViewModel.clean()
searchResults.value = emptyList()
searchResultsChannels.value = emptyList()
searchResultsNotes.value = emptyList()
onlineSearch.clear() onlineSearch.clear()
} }
) { ) {
@@ -260,7 +294,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
) )
} }
if (searchValue.isNotBlank()) { if (searchBarViewModel.isSearching()) {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxHeight(), modifier = Modifier.fillMaxHeight(),
contentPadding = PaddingValues( contentPadding = PaddingValues(
@@ -269,17 +303,17 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
), ),
state = listState state = listState
) { ) {
itemsIndexed(hashtagResults.value, key = { _, item -> "#" + item }) { _, item -> itemsIndexed(searchBarViewModel.hashtagResults.value, key = { _, item -> "#" + item }) { _, item ->
HashtagLine(item) { HashtagLine(item) {
navController.navigate("Hashtag/$item") navController.navigate("Hashtag/$item")
} }
} }
itemsIndexed(searchResults.value, key = { _, item -> "u" + item.pubkeyHex }) { _, item -> itemsIndexed(searchBarViewModel.searchResults.value, key = { _, item -> "u" + item.pubkeyHex }) { _, item ->
UserCompose(item, accountViewModel = accountViewModel, navController = navController) UserCompose(item, accountViewModel = accountViewModel, navController = navController)
} }
itemsIndexed(searchResultsChannels.value, key = { _, item -> "c" + item.idHex }) { _, item -> itemsIndexed(searchBarViewModel.searchResultsChannels.value, key = { _, item -> "c" + item.idHex }) { _, item ->
ChannelName( ChannelName(
channelIdHex = item.idHex, channelIdHex = item.idHex,
channelPicture = item.profilePicture(), channelPicture = item.profilePicture(),
@@ -296,7 +330,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
) )
} }
itemsIndexed(searchResultsNotes.value, key = { _, item -> "n" + item.idHex }) { _, item -> itemsIndexed(searchBarViewModel.searchResultsNotes.value, key = { _, item -> "n" + item.idHex }) { _, item ->
NoteCompose(item, accountViewModel = accountViewModel, navController = navController) NoteCompose(item, accountViewModel = accountViewModel, navController = navController)
} }
} }