Show local profiles on search
This commit is contained in:
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?
|
||||||
|
|||||||
+8
@@ -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
@@ -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"
|
||||||
|
|||||||
Reference in new issue
Block a user