Show filtered list of locally stored profiles when search is being carried out.
This commit is contained in:
@@ -211,6 +211,11 @@ abstract class NostrDao(
|
||||
synchronizeNostrEventRequests
|
||||
)
|
||||
}
|
||||
|
||||
nostrEvent.toProfile()?.let { profile ->
|
||||
database.profileDao().upsert(profile)
|
||||
}
|
||||
|
||||
nostrEvent.toPost()?.let { post ->
|
||||
if (post.replyToId != null) {
|
||||
// Find or create placeHolder note that is replyTo
|
||||
@@ -232,11 +237,20 @@ abstract class NostrDao(
|
||||
database.postDao().upsert(post)
|
||||
}
|
||||
|
||||
nostrEvent.toProfile()?.let { profile ->
|
||||
database.profileDao().upsert(profile)
|
||||
}
|
||||
|
||||
nostrEvent.toReaction()?.let { reaction ->
|
||||
val replyingToPost = database.postDao().getPostById(reaction.replyToId)
|
||||
|
||||
if (replyingToPost == null) {
|
||||
// Persist PlaceHolder and sync
|
||||
database.postDao().insert(
|
||||
Post(
|
||||
id = reaction.replyToId,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync handled by tagged events...
|
||||
content = reaction.replyToId,
|
||||
profilePublicKey = nostrEvent.pubKey // Will get overwriting by sync handled by tagged events...
|
||||
)
|
||||
)
|
||||
}
|
||||
database.reactionDao().upsert(reaction)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package ac.aux.compose.database.dao
|
||||
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import ac.aux.compose.database.model.intermdiate.LocalAccount
|
||||
import ac.aux.compose.database.model.intermdiate.LocalProfile
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
@@ -11,6 +13,9 @@ interface ProfileDao {
|
||||
@Query("SELECT * FROM Profile LIMIT :limit")
|
||||
fun getAllProfiles(limit: Int = 21): List<Profile>
|
||||
|
||||
@Query("SELECT * FROM NostrEvent WHERE kind = 0 AND content LIKE '%' || :searchTerm || '%' LIMIT :limit")
|
||||
fun filterProfiles(searchTerm: String, limit: Int = 21): List<LocalProfile>
|
||||
|
||||
@Query("SELECT * FROM Profile WHERE publicKey = :publicKey")
|
||||
fun getProfileByPublicKey(publicKey: String): Profile?
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package ac.aux.compose.database.dao
|
||||
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.database.model.UnsignedNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalProfile
|
||||
import ac.aux.compose.database.model.intermdiate.LocalAccount
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -24,7 +22,7 @@ interface UnsignedNostrEventDao {
|
||||
suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent): Long
|
||||
|
||||
@Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey")
|
||||
fun observeProfile(publicKey: String): Flow<LocalProfile?>
|
||||
fun observeProfile(publicKey: String): Flow<LocalAccount?>
|
||||
|
||||
@Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedAt IS NULL") // TODO: where nostrEvent.unsignedNostrEventId is 0
|
||||
fun observeUnsignedNostrEvents(publicKey: String): Flow<UnsignedNostrEvent?>
|
||||
|
||||
@@ -52,4 +52,8 @@ data class Profile(
|
||||
override val viewedAt: Instant? = null,
|
||||
override val deletedAt: Instant? = null,
|
||||
override val broadcastedAt: Instant? = null,
|
||||
): NostrEventEntity, TimestampedEntity, LocalStoreEntity, UserViewableEntity, BroadcastableEntity, SoftDeletableEntity
|
||||
): NostrEventEntity, TimestampedEntity, LocalStoreEntity, UserViewableEntity, BroadcastableEntity, SoftDeletableEntity {
|
||||
fun humanReadableNameOrPubkey(): String {
|
||||
return displayName ?: userName ?: nip05 ?: twitter ?: publicKey
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package ac.aux.compose.database.model.intermdiate
|
||||
|
||||
import ac.aux.compose.database.model.BroadcastNostrEventReceipt
|
||||
import ac.aux.compose.database.model.BroadcastNostrEventRequest
|
||||
import ac.aux.compose.database.model.NostrEvent
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import ac.aux.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.aux.compose.database.model.UnsignedNostrEvent
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
|
||||
data class LocalAccount(
|
||||
@Embedded val unsignedNostrEvent: UnsignedNostrEvent?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "unsignedNostrEventId"
|
||||
)
|
||||
val nostrEvent: NostrEvent?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "pubKey",
|
||||
entityColumn = "publicKey"
|
||||
)
|
||||
val profile: Profile?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "unsignedNostrEventId"
|
||||
)
|
||||
val broadcastNostrEventRequest: BroadcastNostrEventRequest?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "unsignedNostrEventId"
|
||||
)
|
||||
val broadcastNostrEventReceipt: BroadcastNostrEventReceipt?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "unsignedNostrEventId"
|
||||
)
|
||||
val synchronizeNostrEventRequests: List<SynchronizeNostrEventRequest>,
|
||||
|
||||
)
|
||||
@@ -10,36 +10,11 @@ import androidx.room.Embedded
|
||||
import androidx.room.Relation
|
||||
|
||||
data class LocalProfile(
|
||||
@Embedded val unsignedNostrEvent: UnsignedNostrEvent?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "unsignedNostrEventId"
|
||||
)
|
||||
val nostrEvent: NostrEvent?,
|
||||
@Embedded val nostrEvent: NostrEvent,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "pubKey",
|
||||
entityColumn = "publicKey"
|
||||
)
|
||||
val profile: Profile?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "unsignedNostrEventId"
|
||||
)
|
||||
val broadcastNostrEventRequest: BroadcastNostrEventRequest?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "unsignedNostrEventId"
|
||||
)
|
||||
val broadcastNostrEventReceipt: BroadcastNostrEventReceipt?,
|
||||
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "unsignedNostrEventId"
|
||||
)
|
||||
val synchronizeNostrEventRequests: List<SynchronizeNostrEventRequest>,
|
||||
|
||||
val profile: Profile,
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ import ac.aux.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.aux.compose.database.model.UnsignedNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalBroadcastNostrEventRequest
|
||||
import ac.aux.compose.database.model.intermdiate.LocalNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalAccount
|
||||
import ac.aux.compose.database.model.intermdiate.LocalProfile
|
||||
import ac.aux.compose.database.model.typealiases.SynchronizationFilterArray
|
||||
import ac.aux.compose.nostr.Relays
|
||||
@@ -49,7 +50,7 @@ class DatabaseNostrRepository(
|
||||
|
||||
val logger = Logger.withTag(TAG)
|
||||
|
||||
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> {
|
||||
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalAccount?> {
|
||||
return database.unsignedNostrEventDao().observeProfile(publicKey)
|
||||
}
|
||||
|
||||
@@ -326,6 +327,10 @@ class DatabaseNostrRepository(
|
||||
return database.profileDao().getAllProfiles()
|
||||
}
|
||||
|
||||
override suspend fun filterProfiles(searchTerm: String): List<LocalProfile> {
|
||||
return database.profileDao().filterProfiles(searchTerm = searchTerm)
|
||||
}
|
||||
|
||||
override suspend fun getRecentSearches(): List<RecentSearch> {
|
||||
return database.recentSearchDao().getAllRecentSearches()
|
||||
}
|
||||
|
||||
@@ -9,13 +9,14 @@ import ac.aux.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.aux.compose.database.model.UnsignedNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalBroadcastNostrEventRequest
|
||||
import ac.aux.compose.database.model.intermdiate.LocalNostrEvent
|
||||
import ac.aux.compose.database.model.intermdiate.LocalAccount
|
||||
import ac.aux.compose.database.model.intermdiate.LocalProfile
|
||||
import ac.aux.compose.database.model.typealiases.SynchronizationFilterArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NostrRepository {
|
||||
suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?>
|
||||
suspend fun observeProfile(publicKey: HexKey): Flow<LocalAccount?>
|
||||
|
||||
suspend fun observeUnsignedNostrEvents(publicKey: HexKey): Flow<UnsignedNostrEvent?>
|
||||
|
||||
@@ -75,6 +76,8 @@ interface NostrRepository {
|
||||
|
||||
suspend fun searchableProfiles(): List<Profile>
|
||||
|
||||
suspend fun filterProfiles(searchTerm: String): List<LocalProfile>
|
||||
|
||||
suspend fun getRecentSearches(): List<RecentSearch>
|
||||
|
||||
suspend fun removeRecentSearch(recentSearch: RecentSearch)
|
||||
@@ -83,7 +86,7 @@ interface NostrRepository {
|
||||
|
||||
companion object {
|
||||
val NO_OP_NOSTR_REPOSITORY = object : NostrRepository {
|
||||
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalProfile?> {
|
||||
override suspend fun observeProfile(publicKey: HexKey): Flow<LocalAccount?> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@@ -173,6 +176,10 @@ interface NostrRepository {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun filterProfiles(searchTerm: String): List<LocalProfile> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getRecentSearches(): List<RecentSearch> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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 {
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@ fun FeedScreen(
|
||||
when(val feedListUIState = feedListViewModel.feedListUIState) {
|
||||
is FeedListUIState.Loading -> {
|
||||
LoadingDataIndicator()
|
||||
|
||||
}
|
||||
FeedListUIState.Error -> {
|
||||
Text("Something went wrong")
|
||||
@@ -198,7 +197,7 @@ fun FeedScreen(
|
||||
if (feedListViewModel.isSynchronizationPending.value) {
|
||||
item {
|
||||
Spacer(
|
||||
modifier = Modifier.height(50.dp)
|
||||
modifier = Modifier.height(30.dp)
|
||||
)
|
||||
LoadingIndicator()
|
||||
}
|
||||
|
||||
@@ -97,34 +97,6 @@ fun SearchScreen(
|
||||
searchRepository = searchRepository
|
||||
),
|
||||
)
|
||||
|
||||
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()
|
||||
// Controls expansion state of the search bar
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -149,7 +121,10 @@ fun SearchScreen(
|
||||
inputField = {
|
||||
SearchBarDefaults.InputField(
|
||||
query = textFieldState.text.toString(),
|
||||
onQueryChange = { textFieldState.edit { replace(0, length, it) } },
|
||||
onQueryChange = {
|
||||
textFieldState.edit { replace(0, length, it) }
|
||||
searchViewModel.filterLocalProfiles(it)
|
||||
},
|
||||
onSearch = {
|
||||
if (textFieldState.text.isBlank()) {
|
||||
expanded = false
|
||||
@@ -265,9 +240,9 @@ fun SearchScreen(
|
||||
|
||||
)
|
||||
}
|
||||
itemsIndexed(searchResults) { index, item ->
|
||||
itemsIndexed(searchViewModel.filteredProfiles) { index, profile ->
|
||||
ListItem(
|
||||
headlineContent = { Text(item) },
|
||||
headlineContent = { Text(profile.humanReadableNameOrPubkey()) },
|
||||
supportingContent = {
|
||||
Text("Profile")
|
||||
},
|
||||
@@ -275,7 +250,7 @@ fun SearchScreen(
|
||||
IconButton(
|
||||
onClick = {
|
||||
onNavigateToProfile.invoke(
|
||||
ImplementationPendingRoute("Profile result")
|
||||
NostrEventDetailRoute(profile.nostrEventId)
|
||||
)
|
||||
}
|
||||
) {
|
||||
@@ -287,10 +262,8 @@ fun SearchScreen(
|
||||
},
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
onNavigateToSearchResult.invoke(
|
||||
SearchResultRoute(
|
||||
item
|
||||
)
|
||||
onNavigateToProfile.invoke(
|
||||
NostrEventDetailRoute(profile.nostrEventId)
|
||||
)
|
||||
}
|
||||
.fillMaxWidth(),
|
||||
|
||||
@@ -2,9 +2,6 @@ package ac.aux.compose.ui.view.model
|
||||
|
||||
import ac.aux.compose.database.model.Profile
|
||||
import ac.aux.compose.database.model.RecentSearch
|
||||
import ac.aux.compose.database.model.SynchronizeNostrEventRequest
|
||||
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.SearchUIState
|
||||
@@ -18,10 +15,6 @@ import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -39,6 +32,9 @@ class SearchViewModel(
|
||||
var searchableProfiles = mutableStateListOf<Profile>()
|
||||
private set
|
||||
|
||||
var filteredProfiles = mutableStateListOf<Profile>()
|
||||
private set
|
||||
|
||||
var recentSearches = mutableStateListOf<RecentSearch>()
|
||||
private set
|
||||
|
||||
@@ -47,6 +43,17 @@ class SearchViewModel(
|
||||
loadRecentSearches()
|
||||
}
|
||||
|
||||
fun filterLocalProfiles(searchTerm: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val filteredLocalProfiles = nostrRepository.filterProfiles(searchTerm = searchTerm)
|
||||
|
||||
filteredProfiles.clear()
|
||||
filteredProfiles.addAll(
|
||||
filteredLocalProfiles.map { it.profile }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSearchableProfiles() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
searchableProfiles.addAll(
|
||||
|
||||
Reference in New Issue
Block a user