From e3aae6620d831e98f81a57e2af2c1bdb2eca9635 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Wed, 1 Apr 2026 23:36:33 +0200 Subject: [PATCH] Show local profiles on search --- .../ac/aux/compose/database/dao/ProfileDao.kt | 5 +- .../repository/DatabaseNostrRepository.kt | 8 ++ .../repository/DatabaseSearchRepository.kt | 1 + .../aux/compose/repository/NostrRepository.kt | 13 +++ .../compose/repository/SearchRepository.kt | 2 + .../aux/compose/ui/composable/SearchScreen.kt | 94 ++++++++++++------- .../compose/ui/view/model/SearchViewModel.kt | 15 +++ 7 files changed, 100 insertions(+), 38 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/ProfileDao.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/ProfileDao.kt index 2402dd7c..795443b2 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/ProfileDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/dao/ProfileDao.kt @@ -3,14 +3,13 @@ package ac.aux.compose.database.dao import ac.aux.compose.database.model.Profile import androidx.room.Dao import androidx.room.Insert -import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Upsert @Dao interface ProfileDao { - @Query("SELECT * FROM Profile") - fun getAllPosts(): List + @Query("SELECT * FROM Profile LIMIT :limit") + fun getAllProfiles(limit: Int = 21): List @Query("SELECT * FROM Profile WHERE publicKey = :publicKey") fun getProfileByPublicKey(publicKey: String): Profile? diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt index 7b7a8cbb..7a72ed78 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseNostrRepository.kt @@ -320,4 +320,12 @@ class DatabaseNostrRepository( return database.nostrEventDao().observeNostrEventById(nostrEventId) } + override suspend fun searchableProfiles(): List { + return database.profileDao().getAllProfiles() + } + + override suspend fun recentSearches(): List { + TODO("Not yet implemented") + } + } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseSearchRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseSearchRepository.kt index 146b1858..a8b17775 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseSearchRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/database/repository/DatabaseSearchRepository.kt @@ -1,6 +1,7 @@ package ac.aux.compose.database.repository import ac.aux.compose.database.AuxDatabase +import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.SynchronizeNostrEventRequest import ac.aux.compose.repository.SearchRepository diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt index 8e988ebb..d6ac8f7f 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrRepository.kt @@ -71,6 +71,11 @@ interface NostrRepository { suspend fun observeLocalNostrEventById(nostrEventId: String): Flow + suspend fun searchableProfiles(): List + + suspend fun recentSearches(): List + + companion object { val NO_OP_NOSTR_REPOSITORY = object : NostrRepository { override suspend fun observeProfile(publicKey: HexKey): Flow { @@ -158,6 +163,14 @@ interface NostrRepository { override suspend fun observeLocalNostrEventById(nostrEventId: String): Flow { TODO("Not yet implemented") } + + override suspend fun searchableProfiles(): List { + TODO("Not yet implemented") + } + + override suspend fun recentSearches(): List { + TODO("Not yet implemented") + } } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/SearchRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/SearchRepository.kt index db371af3..e6f04029 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/SearchRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/SearchRepository.kt @@ -1,6 +1,8 @@ package ac.aux.compose.repository +import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.SynchronizeNostrEventRequest +import ac.aux.compose.database.model.intermdiate.LocalProfile interface SearchRepository { diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/SearchScreen.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/SearchScreen.kt index c6a8730d..88a9ce82 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/SearchScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/SearchScreen.kt @@ -3,6 +3,7 @@ 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.navigation.routes.SearchResultRoute import ac.aux.compose.ui.theme.AuxTheme @@ -18,6 +19,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow @@ -43,8 +45,10 @@ import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExpandedFullScreenSearchBar import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme @@ -72,11 +76,12 @@ import androidx.compose.ui.semantics.isTraversalGroup import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.traversalIndex import androidx.compose.ui.text.style.TextAlign +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 -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun SearchScreen( initialSearchUIState: SearchUIState, @@ -182,7 +187,7 @@ fun SearchScreen( ) val textFieldState = rememberTextFieldState() // Controls expansion state of the search bar - var expanded by rememberSaveable { mutableStateOf(true) } + var expanded by rememberSaveable { mutableStateOf(false) } Scaffold { innerPadding -> Column( @@ -359,50 +364,69 @@ fun SearchScreen( } } else { - // Display search results in a scrollable column + + + LazyColumn { + // Display search results in a scrollable column item { - Column( - modifier = Modifier.padding(10.dp), - - ) { - Text( - text = "Profiles", - style = MaterialTheme.typography.titleMedium - ) - } - - } - item { - LazyRow { - items(profileSearches) { profile -> + searchViewModel.searchableProfiles.let { searchableProfiles -> + if (searchableProfiles.isNotEmpty()) { Column( - modifier = Modifier.padding(10.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalAlignment = Alignment.Start ) { - IconButton( - onClick = { - // TODO: Open profile... - onNavigateToProfile.invoke( - ImplementationPendingRoute("Profile search result") - ) - } + Column( + modifier = Modifier.padding(10.dp), ) { - Icon( - Icons.Default.Person, - contentDescription = profile + Text( + text = "Profiles", + style = MaterialTheme.typography.titleMedium ) } - Text( - text = profile, - style = MaterialTheme.typography.labelSmall - ) + LazyRow { + items(searchViewModel.searchableProfiles) { profile -> + val profileName = profile.displayName ?: profile.userName ?: profile.nip05 ?: profile.nostrEventId + Column( + modifier = Modifier.padding(10.dp).width(70.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + IconButton( + modifier = Modifier + .size(IconButtonDefaults.mediumContainerSize()), + onClick = { + onNavigateToProfile.invoke( + NostrEventDetailRoute( + profile.nostrEventId + ) + ) + } + ) { + Icon( + modifier = Modifier.size(70.dp), + imageVector = Icons.Default.Person, + contentDescription = profileName + ) + } + + Text( + text = profileName, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.typography.labelSmall + ) + } + + } + + } + } } - } } item { diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/SearchViewModel.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/SearchViewModel.kt index 89ac2b02..49a67792 100755 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/SearchViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/SearchViewModel.kt @@ -1,6 +1,8 @@ package ac.aux.compose.ui.view.model +import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.SynchronizeNostrEventRequest +import ac.aux.compose.database.model.intermdiate.LocalProfile import ac.aux.compose.database.model.types.SynchronizationFilter import ac.aux.compose.managers.SeedManager import ac.aux.compose.nostr.Relays @@ -9,6 +11,7 @@ import ac.aux.compose.repository.SearchRepository import ac.aux.compose.ui.view.state.SearchUIState import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel @@ -36,7 +39,12 @@ class SearchViewModel( var searchUIState: SearchUIState by mutableStateOf(initialSearchUIState) private set + var searchableProfiles = mutableStateListOf() + private set + init { + loadSearchableProfiles() + } fun performSearch(searchQuery: String) { viewModelScope.launch(Dispatchers.IO) { // Sync Notifications... might want to also run this in the background @@ -63,6 +71,13 @@ class SearchViewModel( } } + fun loadSearchableProfiles() { + viewModelScope.launch(Dispatchers.IO) { + searchableProfiles.addAll( + nostrRepository.searchableProfiles() + ) + } + } companion object { const val TAG = "SearchViewModel"