Compare commits
3 Commits
master
...
8ea66d1769
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ea66d1769 | |||
| 38b704fe18 | |||
| e0701504aa |
@@ -155,7 +155,7 @@ fun HomeScreen() {
|
|||||||
onPauseOrDispose { }
|
onPauseOrDispose { }
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(authState.signerRequired) {
|
||||||
chatViewModel.refreshChatRooms()
|
chatViewModel.refreshChatRooms()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ data class Room(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun new(rumor: UnsignedEvent, userPubkey: PublicKey): Room {
|
fun new(rumor: UnsignedEvent, userPubkey: PublicKey, id: Long = rumor.roomId()): Room {
|
||||||
val id = rumor.roomId()
|
|
||||||
val createdAt = rumor.createdAt()
|
val createdAt = rumor.createdAt()
|
||||||
val subject = rumor.tags().toVec().find { it.kind() == "subject" }?.content()
|
val subject = rumor.tags().toVec().find { it.kind() == "subject" }?.content()
|
||||||
|
|
||||||
|
|||||||
@@ -164,38 +164,35 @@ class MessageManager(private val nostr: Nostr) {
|
|||||||
try {
|
try {
|
||||||
val userPubkey =
|
val userPubkey =
|
||||||
signer.getPublicKeyAsync() ?: throw IllegalStateException("User not signed in")
|
signer.getPublicKeyAsync() ?: throw IllegalStateException("User not signed in")
|
||||||
|
|
||||||
val kind = Kind.fromStd(KindStandard.APPLICATION_SPECIFIC_DATA)
|
val kind = Kind.fromStd(KindStandard.APPLICATION_SPECIFIC_DATA)
|
||||||
val kTag = SingleLetterTag.lowercase(Alphabet.K)
|
val kTag = SingleLetterTag.lowercase(Alphabet.K)
|
||||||
|
|
||||||
// Get all DM events
|
// Get all DM events
|
||||||
val filter = Filter().kind(kind).customTags(kTag, listOf("14", "dm"))
|
val filter = Filter().kind(kind).customTags(kTag, listOf("14", "dm"))
|
||||||
val events = client?.database()?.query(filter)
|
val events = client?.database()?.query(filter)?.toVec() ?: return null
|
||||||
|
|
||||||
// Collect rooms
|
// Collect rooms
|
||||||
val roomsMap: MutableMap<Long, Room> = mutableMapOf()
|
val roomsMap: MutableMap<Long, Room> = mutableMapOf()
|
||||||
|
|
||||||
events
|
events
|
||||||
?.toVec()
|
.map { UnsignedEvent.fromJson(it.content()) }
|
||||||
?.map { UnsignedEvent.fromJson(it.content()) }
|
.filter { it.tags().publicKeys().isNotEmpty() }
|
||||||
?.filter { it.tags().publicKeys().isNotEmpty() }
|
.forEach { rumor ->
|
||||||
?.forEach { event ->
|
val id = rumor.roomId()
|
||||||
val newRoom = Room.new(rumor = event, userPubkey = userPubkey)
|
val isFromMe = rumor.author() == userPubkey
|
||||||
val existingRoom = roomsMap[newRoom.id]
|
val existing = roomsMap[id]
|
||||||
|
val createdAt = rumor.createdAt()
|
||||||
|
|
||||||
// Check if the room already exists
|
// If the room is new or the current rumor is newer than the existing one
|
||||||
if (existingRoom == null || newRoom.createdAt.asSecs() > existingRoom.createdAt.asSecs()) {
|
if (existing == null || createdAt.asSecs() > existing.createdAt.asSecs()) {
|
||||||
val rTag = SingleLetterTag.lowercase(Alphabet.R)
|
// A room is "Ongoing" if it was already marked as such or if the current rumor is from the user
|
||||||
val filter = Filter().kind(kind).pubkey(userPubkey)
|
val isOngoing = (existing?.kind == RoomKind.Ongoing) || isFromMe
|
||||||
.customTag(rTag, newRoom.id.toString())
|
val room = Room.new(rumor = rumor, userPubkey = userPubkey, id = id)
|
||||||
|
roomsMap[id] = if (isOngoing) room.copy(kind = RoomKind.Ongoing) else room
|
||||||
// Determine if it's an ongoing room
|
} else if (isFromMe && existing.kind != RoomKind.Ongoing) {
|
||||||
val isOngoing =
|
// If it's an older rumor but sent by the user, mark the room as Ongoing
|
||||||
client?.database()?.query(filter)?.toVec()?.isNotEmpty() ?: false
|
roomsMap[id] = existing.copy(kind = RoomKind.Ongoing)
|
||||||
|
|
||||||
// Append room to map
|
|
||||||
roomsMap[newRoom.id] =
|
|
||||||
if (isOngoing) newRoom.copy(kind = RoomKind.Ongoing) else newRoom
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import su.reya.coop.repository.MediaRepository
|
|||||||
import su.reya.coop.roomId
|
import su.reya.coop.roomId
|
||||||
|
|
||||||
data class ChatState(
|
data class ChatState(
|
||||||
val rooms: Set<Room> = emptySet(),
|
val rooms: Map<Long, Room> = emptyMap(),
|
||||||
val isSyncing: Boolean = false,
|
val isSyncing: Boolean = false,
|
||||||
val isPartialProcessedGiftWrap: Boolean = false,
|
val isPartialProcessedGiftWrap: Boolean = false,
|
||||||
)
|
)
|
||||||
@@ -48,8 +48,8 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
private val _sentReports = MutableSharedFlow<Map<EventId, List<RelayUrl>>>()
|
private val _sentReports = MutableSharedFlow<Map<EventId, List<RelayUrl>>>()
|
||||||
val sentReport = _sentReports.asSharedFlow()
|
val sentReport = _sentReports.asSharedFlow()
|
||||||
|
|
||||||
val chatRooms = state.map { it.rooms }
|
val chatRooms = state.map { it.rooms.values.sortedByDescending { it.createdAt.asSecs() } }
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptySet())
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||||
|
|
||||||
val isSyncing = state.map { it.isSyncing }
|
val isSyncing = state.map { it.isSyncing }
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
@@ -69,8 +69,8 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
_state.update { it.copy(isPartialProcessedGiftWrap = true) }
|
_state.update { it.copy(isPartialProcessedGiftWrap = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh UI every 10 messages OR when sync is fully done
|
// Refresh UI every 100 messages OR when sync is fully done
|
||||||
if (syncState.processedCount % 10 == 0 || !syncState.isSyncing) {
|
if (syncState.processedCount % 100 == 0 || !syncState.isSyncing) {
|
||||||
refreshChatRooms()
|
refreshChatRooms()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,16 +80,12 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
launch {
|
launch {
|
||||||
nostr.newEvents.collect { event ->
|
nostr.newEvents.collect { event ->
|
||||||
val roomId = event.roomId()
|
val roomId = event.roomId()
|
||||||
val existingRoom = _state.value.rooms.firstOrNull { it.id == roomId }
|
val existingRoom = _state.value.rooms[roomId]
|
||||||
|
|
||||||
if (existingRoom == null) {
|
if (existingRoom == null) {
|
||||||
val currentUser = nostr.signer.getPublicKeyAsync() ?: return@collect
|
val currentUser = nostr.signer.getPublicKeyAsync() ?: return@collect
|
||||||
val newRoom = Room.new(event, currentUser)
|
val newRoom = Room.new(event, currentUser)
|
||||||
_state.update {
|
_state.update { it.copy(rooms = it.rooms + (newRoom.id to newRoom)) }
|
||||||
it.copy(
|
|
||||||
rooms = (it.rooms + newRoom).sortedDescending().toSet()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
updateRoomList(roomId, event)
|
updateRoomList(roomId, event)
|
||||||
}
|
}
|
||||||
@@ -97,9 +93,6 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
_newEvents.emit(event)
|
_newEvents.emit(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial load of rooms
|
|
||||||
refreshChatRooms()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +113,7 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
|
|
||||||
// Check if the room already exists
|
// Check if the room already exists
|
||||||
val id = rumor.roomId()
|
val id = rumor.roomId()
|
||||||
val existingRoom = _state.value.rooms.firstOrNull { it.id == id }
|
val existingRoom = _state.value.rooms[id]
|
||||||
|
|
||||||
// If the room already exists, return its ID
|
// If the room already exists, return its ID
|
||||||
if (existingRoom != null) {
|
if (existingRoom != null) {
|
||||||
@@ -131,7 +124,7 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
val room = Room.new(rumor, currentUser)
|
val room = Room.new(rumor, currentUser)
|
||||||
|
|
||||||
// Update the chat rooms state
|
// Update the chat rooms state
|
||||||
_state.update { it.copy(rooms = (it.rooms + room).sortedDescending().toSet()) }
|
_state.update { it.copy(rooms = it.rooms + (room.id to room)) }
|
||||||
|
|
||||||
return room.id
|
return room.id
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -140,20 +133,16 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getChatRoom(id: Long): Room? {
|
fun getChatRoom(id: Long): Room? {
|
||||||
return _state.value.rooms.firstOrNull { it.id == id }
|
return _state.value.rooms[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun refreshChatRooms() {
|
suspend fun refreshChatRooms() {
|
||||||
try {
|
try {
|
||||||
val rooms = nostr.messages.getChatRooms() ?: emptySet()
|
val rooms = nostr.messages.getChatRooms() ?: emptySet()
|
||||||
_state.update { currentState ->
|
_state.update { currentState ->
|
||||||
val merged = currentState.rooms.associateBy { it.id }.toMutableMap()
|
val newMap = currentState.rooms.toMutableMap()
|
||||||
// Add or update rooms from the database
|
rooms.forEach { room -> newMap[room.id] = room }
|
||||||
rooms.forEach { room ->
|
currentState.copy(rooms = newMap)
|
||||||
merged[room.id] = room
|
|
||||||
}
|
|
||||||
// Return as a sorted set to maintain UI consistency
|
|
||||||
currentState.copy(rooms = merged.values.sortedDescending().toSet())
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showError("Error: ${e.message}")
|
showError("Error: ${e.message}")
|
||||||
@@ -235,24 +224,19 @@ class ChatViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
|
|
||||||
private fun updateRoomList(roomId: Long, newMessage: UnsignedEvent) {
|
private fun updateRoomList(roomId: Long, newMessage: UnsignedEvent) {
|
||||||
_state.update { currentState ->
|
_state.update { currentState ->
|
||||||
val updatedRooms = currentState.rooms.map { room ->
|
val room = currentState.rooms[roomId] ?: return@update currentState
|
||||||
if (room.id == roomId) {
|
val updatedRoom = room.copy(
|
||||||
room.copy(
|
lastMessage = newMessage.content(),
|
||||||
lastMessage = newMessage.content(),
|
createdAt = newMessage.createdAt()
|
||||||
createdAt = newMessage.createdAt()
|
)
|
||||||
)
|
currentState.copy(rooms = currentState.rooms + (roomId to updatedRoom))
|
||||||
} else {
|
|
||||||
room
|
|
||||||
}
|
|
||||||
}.sortedDescending().toSet()
|
|
||||||
currentState.copy(rooms = updatedRooms)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetInternalState() {
|
fun resetInternalState() {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
rooms = emptySet(),
|
rooms = emptyMap(),
|
||||||
isPartialProcessedGiftWrap = false,
|
isPartialProcessedGiftWrap = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class NostrViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
private val profiles = mutableMapOf<PublicKey, MutableStateFlow<Profile?>>()
|
private val profiles = mutableMapOf<PublicKey, MutableStateFlow<Profile?>>()
|
||||||
private val metadataRequestChannel = Channel<PublicKey>(Channel.UNLIMITED)
|
private val metadataRequestChannel = Channel<PublicKey>(Channel.UNLIMITED)
|
||||||
private val seenPublicKeys = mutableSetOf<PublicKey>()
|
private val seenPublicKeys = mutableSetOf<PublicKey>()
|
||||||
|
|
||||||
val isRelayListEmpty = appState.map { it.isRelayListEmpty }
|
val isRelayListEmpty = appState.map { it.isRelayListEmpty }
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
|
|
||||||
@@ -158,12 +158,14 @@ class NostrViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
// Wait until the client is ready
|
// Wait until the client is ready
|
||||||
nostr.waitUntilInitialized()
|
nostr.waitUntilInitialized()
|
||||||
|
val cache = nostr.profiles.getAllCacheMetadata()
|
||||||
|
|
||||||
nostr.profiles.getAllCacheMetadata().forEach { (pubkey, metadata) ->
|
profilesMutex.withLock {
|
||||||
// Update the metadata state
|
cache.forEach { (pubkey, metadata) ->
|
||||||
updateMetadata(pubkey, Profile(pubkey, metadata))
|
val profile = Profile(pubkey, metadata)
|
||||||
// Update seenPublicKeys to avoid duplicate requests
|
profiles.getOrPut(pubkey) { MutableStateFlow(null) }.value = profile
|
||||||
seenPublicKeys.add(pubkey)
|
seenPublicKeys.add(pubkey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,11 +198,9 @@ class NostrViewModel(private val nostr: Nostr) : BaseViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMetadata(pubkey: PublicKey, profile: Profile) {
|
private suspend fun updateMetadata(pubkey: PublicKey, profile: Profile) {
|
||||||
viewModelScope.launch {
|
profilesMutex.withLock {
|
||||||
profilesMutex.withLock {
|
profiles.getOrPut(pubkey) { MutableStateFlow(null) }.value = profile
|
||||||
profiles.getOrPut(pubkey) { MutableStateFlow(null) }.value = profile
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user