Show local profiles on search

This commit is contained in:
Kgothatso Ngako committed 2026-04-01 23:36:33 +02:00
1 parent 8899cfc84e
commit e3aae6620d
7 files changed
+100 -38

No files matched your search

@@ -3,14 +3,13 @@ package ac.aux.compose.database.dao
import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.Profile
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Upsert import androidx.room.Upsert
@Dao @Dao
interface ProfileDao { interface ProfileDao {
@Query("SELECT * FROM Profile") @Query("SELECT * FROM Profile LIMIT :limit")
fun getAllPosts(): List<Profile> fun getAllProfiles(limit: Int = 21): List<Profile>
@Query("SELECT * FROM Profile WHERE publicKey = :publicKey") @Query("SELECT * FROM Profile WHERE publicKey = :publicKey")
fun getProfileByPublicKey(publicKey: String): Profile? fun getProfileByPublicKey(publicKey: String): Profile?
@@ -320,4 +320,12 @@ class DatabaseNostrRepository(
return database.nostrEventDao().observeNostrEventById(nostrEventId) return database.nostrEventDao().observeNostrEventById(nostrEventId)
} }
override suspend fun searchableProfiles(): List<Profile> {
return database.profileDao().getAllProfiles()
}
override suspend fun recentSearches(): List<String> {
TODO("Not yet implemented")
}
} }
@@ -1,6 +1,7 @@
package ac.aux.compose.database.repository package ac.aux.compose.database.repository
import ac.aux.compose.database.AuxDatabase import ac.aux.compose.database.AuxDatabase
import ac.aux.compose.database.model.Profile
import ac.aux.compose.database.model.SynchronizeNostrEventRequest import ac.aux.compose.database.model.SynchronizeNostrEventRequest
import ac.aux.compose.repository.SearchRepository import ac.aux.compose.repository.SearchRepository
@@ -71,6 +71,11 @@ interface NostrRepository {
suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?> suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?>
suspend fun searchableProfiles(): List<Profile>
suspend fun recentSearches(): List<String>
companion object { companion object {
val NO_OP_NOSTR_REPOSITORY = object : NostrRepository { val NO_OP_NOSTR_REPOSITORY = object : NostrRepository {
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> { override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> {
@@ -158,6 +163,14 @@ interface NostrRepository {
override suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?> { override suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun searchableProfiles(): List<Profile> {
TODO("Not yet implemented")
}
override suspend fun recentSearches(): List<String> {
TODO("Not yet implemented")
}
} }
} }
} }
@@ -1,6 +1,8 @@
package ac.aux.compose.repository 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.SynchronizeNostrEventRequest
import ac.aux.compose.database.model.intermdiate.LocalProfile
interface SearchRepository { interface SearchRepository {
@@ -3,6 +3,7 @@ package ac.aux.compose.ui.composable
import ac.aux.compose.repository.NostrRepository import ac.aux.compose.repository.NostrRepository
import ac.aux.compose.repository.SearchRepository import ac.aux.compose.repository.SearchRepository
import ac.aux.compose.ui.composable.navigation.routes.ImplementationPendingRoute 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.Route
import ac.aux.compose.ui.composable.navigation.routes.SearchResultRoute import ac.aux.compose.ui.composable.navigation.routes.SearchResultRoute
import ac.aux.compose.ui.theme.AuxTheme 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
@@ -43,8 +45,10 @@ import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExpandedFullScreenSearchBar import androidx.compose.material3.ExpandedFullScreenSearchBar
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme 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.semantics
import androidx.compose.ui.semantics.traversalIndex import androidx.compose.ui.semantics.traversalIndex
import androidx.compose.ui.text.style.TextAlign 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun SearchScreen( fun SearchScreen(
initialSearchUIState: SearchUIState, initialSearchUIState: SearchUIState,
@@ -182,7 +187,7 @@ fun SearchScreen(
) )
val textFieldState = rememberTextFieldState() val textFieldState = rememberTextFieldState()
// Controls expansion state of the search bar // Controls expansion state of the search bar
var expanded by rememberSaveable { mutableStateOf(true) } var expanded by rememberSaveable { mutableStateOf(false) }
Scaffold { innerPadding -> Scaffold { innerPadding ->
Column( Column(
@@ -359,50 +364,69 @@ fun SearchScreen(
} }
} else { } else {
// Display search results in a scrollable column
LazyColumn { LazyColumn {
// Display search results in a scrollable column
item { item {
Column( searchViewModel.searchableProfiles.let { searchableProfiles ->
modifier = Modifier.padding(10.dp), if (searchableProfiles.isNotEmpty()) {
) {
Text(
text = "Profiles",
style = MaterialTheme.typography.titleMedium
)
}
}
item {
LazyRow {
items(profileSearches) { profile ->
Column( Column(
modifier = Modifier.padding(10.dp), modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.Center horizontalAlignment = Alignment.Start
) { ) {
IconButton( Column(
onClick = { modifier = Modifier.padding(10.dp),
// TODO: Open profile...
onNavigateToProfile.invoke(
ImplementationPendingRoute("Profile search result")
)
}
) { ) {
Icon( Text(
Icons.Default.Person, text = "Profiles",
contentDescription = profile style = MaterialTheme.typography.titleMedium
) )
} }
Text( LazyRow {
text = profile, items(searchViewModel.searchableProfiles) { profile ->
style = MaterialTheme.typography.labelSmall 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 { item {
@@ -1,6 +1,8 @@
package ac.aux.compose.ui.view.model 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.SynchronizeNostrEventRequest
import ac.aux.compose.database.model.intermdiate.LocalProfile
import ac.aux.compose.database.model.types.SynchronizationFilter import ac.aux.compose.database.model.types.SynchronizationFilter
import ac.aux.compose.managers.SeedManager import ac.aux.compose.managers.SeedManager
import ac.aux.compose.nostr.Relays 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 ac.aux.compose.ui.view.state.SearchUIState
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -36,7 +39,12 @@ class SearchViewModel(
var searchUIState: SearchUIState by mutableStateOf(initialSearchUIState) var searchUIState: SearchUIState by mutableStateOf(initialSearchUIState)
private set private set
var searchableProfiles = mutableStateListOf<Profile>()
private set
init {
loadSearchableProfiles()
}
fun performSearch(searchQuery: String) { fun performSearch(searchQuery: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
// Sync Notifications... might want to also run this in the background // 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 { companion object {
const val TAG = "SearchViewModel" const val TAG = "SearchViewModel"