Actually perform search

This commit is contained in:
Kgothatso Ngako
2026-04-19 23:15:09 +02:00
parent 45da6eb63e
commit 9358aa07a8
3 changed files with 203 additions and 114 deletions

View File

@@ -2,7 +2,6 @@ package ac.aux.compose.ui.composable
import ac.aux.compose.repository.NostrRepository
import ac.aux.compose.repository.SearchRepository
import ac.aux.compose.ui.composable.navigation.routes.ImplementationPendingRoute
import ac.aux.compose.ui.composable.navigation.routes.NostrEventDetailRoute
import ac.aux.compose.ui.composable.navigation.routes.Route
import ac.aux.compose.ui.composable.widgets.feed.ListView
@@ -20,6 +19,8 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIosNew
@@ -43,8 +44,8 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -57,6 +58,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
@@ -67,6 +69,8 @@ fun SearchResultScreen(
onNavigateBack: () -> Unit,
onNavigateToEvent: (Route) -> Unit
) {
val scope = rememberCoroutineScope()
val searchViewModel: SearchResultViewModel = viewModel(
factory = SearchResultViewModel.factory(
initialSearchResultType = SearchResultType.Posts,
@@ -76,33 +80,6 @@ fun SearchResultScreen(
),
)
val searchResults = listOf(
"Apple",
"Banna",
"Carrot",
"Durian",
"Eggplant",
"Fig",
"Grape",
"Honeydew",
"Jackfruit",
"Kiwi",
"Lemon",
"Mango",
"Nectarine",
"Orange",
"Papaya",
"Quince",
"Raspberry",
"Tangerine",
"Ugli Fruit",
"Vanilla Bean",
"Watermelon",
"Xigua",
"Yellow Passion Fruit",
"Zinfandel",
)
val textFieldState = rememberTextFieldState(searchQuery)
// Controls expansion state of the search bar
var expanded by rememberSaveable { mutableStateOf(true) }
@@ -184,6 +161,8 @@ fun SearchResultScreen(
},
windowInsets = TopAppBarDefaults.windowInsets
) {
val pagerState = rememberPagerState { SearchResultType.entries.size }
PrimaryScrollableTabRow(
modifier = Modifier,
edgePadding = 10.dp,
@@ -194,6 +173,11 @@ fun SearchResultScreen(
selected = searchViewModel.searchResultType == searchResultType,
onClick = {
searchViewModel.updateSearchResultType(searchResultType)
scope.launch {
pagerState.animateScrollToPage(
searchResultType.ordinal
)
}
},
text = {
Text(
@@ -206,22 +190,90 @@ fun SearchResultScreen(
}
}
when (val searchResultListUIState = searchViewModel.searchResultListUIState) {
SearchResultListUIState.Error -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(
modifier = Modifier.height(50.dp)
)
Text(
text = "Something went wrong",
)
HorizontalPager(
state = pagerState
) { pageIndex ->
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
when(SearchResultType.entries[pageIndex]) {
SearchResultType.Posts -> {
Text("Posts")
}
SearchResultType.Articles -> {
Text("Posts")
}
SearchResultType.Profiles -> {
Text("Posts")
}
SearchResultType.Photos -> {
Text("Posts")
}
SearchResultType.Videos -> {
Text("Posts")
}
}
}
is SearchResultListUIState.Loaded -> {
if (searchResultListUIState.localNostrEvents.isEmpty()) {
when (val searchResultListUIState = searchViewModel.searchResultListUIState) {
SearchResultListUIState.Error -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(
modifier = Modifier.height(50.dp)
)
Text(
text = "Something went wrong",
)
}
}
is SearchResultListUIState.Loaded -> {
if (searchResultListUIState.localNostrEvents.isEmpty()) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(
modifier = Modifier.height(50.dp)
)
Text(
text = "No events were found",
)
}
} else {
LazyColumn(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(
items = searchResultListUIState.localNostrEvents,
key = { localNostrEvent -> localNostrEvent.nostrEvent.id}
) { localNostrEvent ->
localNostrEvent.ListView(
onNavigateToEvent = { nostrEventId ->
onNavigateToEvent.invoke(
NostrEventDetailRoute(
nostrEventId = nostrEventId
)
)
}
)
}
}
}
}
SearchResultListUIState.Loading -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
@@ -230,59 +282,29 @@ fun SearchResultScreen(
modifier = Modifier.height(50.dp)
)
Text(
text = "No events were found",
text = "Searching for ${searchViewModel.searchResultType.name.lowercase()} on \"${searchQuery.lowercase()}\"",
textAlign = TextAlign.Center
)
}
} else {
LazyColumn(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(
items = searchResultListUIState.localNostrEvents,
key = { localNostrEvent -> localNostrEvent.nostrEvent.id}
) { localNostrEvent ->
localNostrEvent.ListView(
onNavigateToEvent = { nostrEventId ->
onNavigateToEvent.invoke(
NostrEventDetailRoute(
nostrEventId = nostrEventId
)
)
}
)
}
Spacer(
modifier = Modifier.height(50.dp)
)
LoadingIndicator()
}
}
}
SearchResultListUIState.Loading -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(
modifier = Modifier.height(50.dp)
)
Text(
text = "Searching for ${searchViewModel.searchResultType.name.lowercase()} on \"${searchQuery.lowercase()}\"",
textAlign = TextAlign.Center
)
Spacer(
modifier = Modifier.height(50.dp)
)
LoadingIndicator()
}
LaunchedEffect(true) {
searchViewModel.loadSearchResult()
}
}
}
}
}
}

View File

@@ -78,7 +78,7 @@ fun AuxNavHost(
database = databaseManager.auxDatabase
)
val searchRepository = DatabaseSearchRepository(
database = databaseManager.inMemoryAuxDatabase
database = databaseManager.auxDatabase
)
val exceptionHandler =

View File

@@ -5,7 +5,6 @@ import ac.aux.compose.database.model.types.SynchronizationFilter
import ac.aux.compose.nostr.Relays
import ac.aux.compose.repository.NostrRepository
import ac.aux.compose.repository.SearchRepository
import ac.aux.compose.ui.view.state.FeedListUIState
import ac.aux.compose.ui.view.state.SearchResultListUIState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -16,8 +15,10 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import kotlinx.coroutines.Dispatchers
@@ -43,45 +44,98 @@ class SearchResultViewModel(
private set
fun updateSearchResultType (searchResultType: SearchResultType) {
searchResultListUIState = SearchResultListUIState.Loading
this.searchResultType = searchResultType
reloadSearchResult()
initiateSearchResults(searchResultType)
}
fun reloadSearchResult() {
searchResultListUIState = SearchResultListUIState.Loading
init {
initiateSearchResults(searchResultType)
}
fun loadSearchResult() {
fun initiateSearchResults(searchResultType: SearchResultType) {
val synchronizationFilter = when (searchResultType) {
SearchResultType.Posts -> {
SynchronizationFilter(
kinds = arrayOf(
TextNoteEvent.KIND,
RepostEvent.KIND,
ReactionEvent.KIND,
LnZapEvent.KIND,
),
search = searchQuery,
limit = 50
)
}
SearchResultType.Profiles -> {
SynchronizationFilter(
kinds = arrayOf(
MetadataEvent.KIND,
),
search = searchQuery,
limit = 50
)
}
SearchResultType.Articles -> {
SynchronizationFilter(
kinds = arrayOf(
LongTextNoteEvent.KIND,
),
search = searchQuery,
limit = 50
)
}
SearchResultType.Photos -> {
SynchronizationFilter(
kinds = arrayOf(
TextNoteEvent.KIND,
),
// TODO: filter for tags of pictures...
search = searchQuery,
limit = 50
)
}
SearchResultType.Videos -> {
SynchronizationFilter(
kinds = arrayOf(
TextNoteEvent.KIND,
),
// TODO: filter for tags of videos...
search = searchQuery,
limit = 50
)
}
}
saveSearchQuery(synchronizationFilter)
scheduleSearch(synchronizationFilter)
loadSearchResultFeed(synchronizationFilter)
}
private fun saveSearchQuery(synchronizationFilter: SynchronizationFilter) {
logger.d("saveSearchQuery: $searchQuery")
viewModelScope.launch(Dispatchers.IO) {
nostrRepository.saveSearchQuery(
searchQuery,
// TODO: synchronizationFilterArray
)
// This is something not taking into consideration the results we are actually looking for...
nostrRepository.observeNostrFeed().collect { localNostrEvents ->
searchResultListUIState = SearchResultListUIState.Loaded(
localNostrEvents = localNostrEvents
arrayOf(
synchronizationFilter
)
}
)
}
}
private fun scheduleSearch(synchronizationFilter: SynchronizationFilter) {
logger.d("scheduleSearch: $searchQuery")
viewModelScope.launch(Dispatchers.IO) {
// Sync Notifications... might want to also run this in the background
searchRepository.submitSearch(
Relays.eventPublishRelaySet.map { normalizedRelay ->
Relays.eventFinderRelaySet.map { normalizedRelay ->
SynchronizeNostrEventRequest(
purpose = "search",
synchronizationFilters = arrayOf(
SynchronizationFilter(
kinds = arrayOf(
TextNoteEvent.KIND,
RepostEvent.KIND,
ReactionEvent.KIND,
LnZapEvent.KIND,
),
search = searchQuery,
limit = 50
)
synchronizationFilter
),
relayURL = normalizedRelay.url
)
@@ -90,6 +144,19 @@ class SearchResultViewModel(
}
}
private fun loadSearchResultFeed(synchronizationFilter: SynchronizationFilter) {
logger.d("loadSearchResultFeed: $searchQuery")
viewModelScope.launch(Dispatchers.IO) {
// This is something not taking into consideration the results we are actually looking for...
// TODO: Search local notes using the [synchronizationFilter]
nostrRepository.observeNostrFeed().collect { localNostrEvents ->
searchResultListUIState = SearchResultListUIState.Loaded(
localNostrEvents = localNostrEvents
)
}
}
}
companion object {
const val TAG = "SearchViewModel"