Show local profiles on search

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

View File

@@ -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<Profile>
@Query("SELECT * FROM Profile LIMIT :limit")
fun getAllProfiles(limit: Int = 21): List<Profile>
@Query("SELECT * FROM Profile WHERE publicKey = :publicKey")
fun getProfileByPublicKey(publicKey: String): Profile?

View File

@@ -320,4 +320,12 @@ class DatabaseNostrRepository(
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")
}
}

View File

@@ -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

View File

@@ -71,6 +71,11 @@ interface NostrRepository {
suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?>
suspend fun searchableProfiles(): List<Profile>
suspend fun recentSearches(): List<String>
companion object {
val NO_OP_NOSTR_REPOSITORY = object : NostrRepository {
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> {
@@ -158,6 +163,14 @@ interface NostrRepository {
override suspend fun observeLocalNostrEventById(nostrEventId: String): Flow<LocalNostrEvent?> {
TODO("Not yet implemented")
}
override suspend fun searchableProfiles(): List<Profile> {
TODO("Not yet implemented")
}
override suspend fun recentSearches(): List<String> {
TODO("Not yet implemented")
}
}
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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<Profile>()
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"