diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/HomeScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/HomeScreen.kt index 91fe388..968627d 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/HomeScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/HomeScreen.kt @@ -655,6 +655,8 @@ fun NewRequests(requests: List) { else -> "" } + val totalUnread = requests.sumOf { it.unreadCount } + ListItem( modifier = Modifier.clickable { navigator.navigate(Screen.RequestList) @@ -683,19 +685,31 @@ fun NewRequests(requests: List) { headlineContent = { Text( text = "Requests", - style = MaterialTheme.typography.titleMediumEmphasized + style = MaterialTheme.typography.titleMediumEmphasized.copy( + fontWeight = if (totalUnread > 0) FontWeight.SemiBold else FontWeight.Normal + ) ) }, supportingContent = { if (supportingText.isNotEmpty()) { Text( text = supportingText, - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodyMedium.copy( + fontWeight = if (totalUnread > 0) FontWeight.SemiBold else FontWeight.Normal, + color = if (totalUnread > 0) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.outline + ), maxLines = 1, overflow = TextOverflow.Ellipsis ) } }, + trailingContent = { + if (totalUnread > 0) { + androidx.compose.material3.Badge { + Text(totalUnread.toString()) + } + } + }, colors = ListItemDefaults.colors( containerColor = MaterialTheme.colorScheme.surface ) @@ -721,13 +735,15 @@ fun ChatRoom(room: Room, onClick: () -> Unit) { ) { Text( text = roomState.name, - style = MaterialTheme.typography.titleMediumEmphasized, + style = MaterialTheme.typography.titleMediumEmphasized.copy( + fontWeight = if (room.unreadCount > 0) FontWeight.SemiBold else FontWeight.Normal + ), modifier = Modifier.weight(1f) ) Text( text = room.createdAt.ago(), style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.outline + color = if (room.unreadCount > 0) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline ) } }, @@ -735,12 +751,22 @@ fun ChatRoom(room: Room, onClick: () -> Unit) { if (!room.lastMessage.isNullOrBlank()) { Text( text = room.lastMessage ?: "", - style = MaterialTheme.typography.bodyMedium, + style = MaterialTheme.typography.bodyMedium.copy( + fontWeight = if (room.unreadCount > 0) FontWeight.SemiBold else FontWeight.Normal, + color = if (room.unreadCount > 0) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.outline + ), overflow = TextOverflow.Ellipsis, maxLines = 1, ) } }, + trailingContent = { + if (room.unreadCount > 0) { + androidx.compose.material3.Badge { + Text(room.unreadCount.toString()) + } + } + }, colors = ListItemDefaults.colors( containerColor = MaterialTheme.colorScheme.surface ) diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Room.kt b/shared/src/commonMain/kotlin/su/reya/coop/Room.kt index d761aad..bcfe38f 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/Room.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/Room.kt @@ -30,7 +30,8 @@ data class Room( val subject: String?, val members: Set, val kind: RoomKind = RoomKind.default(), - val lastMessage: String? = null + val lastMessage: String? = null, + val unreadCount: Int = 0 ) : Comparable { override fun compareTo(other: Room): Int { return this.createdAt.asSecs().compareTo(other.createdAt.asSecs()) diff --git a/shared/src/commonMain/kotlin/su/reya/coop/repository/ChatRepository.kt b/shared/src/commonMain/kotlin/su/reya/coop/repository/ChatRepository.kt index 2cabfe3..cb6ec28 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/repository/ChatRepository.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/repository/ChatRepository.kt @@ -130,6 +130,19 @@ class ChatRepository( return _state.value.rooms[id] } + fun markAsRead(roomId: Long) { + _state.update { currentState -> + val rooms = currentState.rooms.toMutableMap() + val room = rooms[roomId] + if (room != null && room.unreadCount > 0) { + rooms[roomId] = room.copy(unreadCount = 0) + currentState.copy(rooms = rooms) + } else { + currentState + } + } + } + fun refreshChatRooms() { scope.launch(defaultDispatcher) { try { @@ -140,10 +153,14 @@ class ChatRepository( val existing = newMap[dbRoom.id] // Only update if the database version is newer or equal if (existing == null || dbRoom.createdAt.asSecs() >= existing.createdAt.asSecs()) { - // Preserve Ongoing kind if already marked as such in memory + // Preserve Ongoing kind and unreadCount status if already marked as such in memory val mergedKind = if (existing?.kind == RoomKind.Ongoing) RoomKind.Ongoing else dbRoom.kind - newMap[dbRoom.id] = dbRoom.copy(kind = mergedKind) + val mergedUnreadCount = existing?.unreadCount ?: 0 + newMap[dbRoom.id] = dbRoom.copy( + kind = mergedKind, + unreadCount = mergedUnreadCount + ) } } currentState.copy(rooms = newMap) @@ -237,14 +254,18 @@ class ChatRepository( if (existingRoom == null) { // New room discovery - val newRoom = Room.new(event, currentUser, roomId).copy(kind = newKind) + val newRoom = Room.new(event, currentUser, roomId).copy( + kind = newKind, + unreadCount = if (isFromMe) 0 else 1 + ) rooms[newRoom.id] = newRoom } else if (event.createdAt().asSecs() >= existingRoom.createdAt.asSecs()) { // Only update preview if message is newer (handles sync/late arrivals) rooms[roomId] = existingRoom.copy( lastMessage = event.content(), createdAt = event.createdAt(), - kind = newKind + kind = newKind, + unreadCount = if (isFromMe) existingRoom.unreadCount else existingRoom.unreadCount + 1 ) } else if (isFromMe && existingRoom.kind != RoomKind.Ongoing) { // Even if it's an older message, if it's from me, the room is ongoing diff --git a/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/ChatScreenViewModel.kt b/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/ChatScreenViewModel.kt index bec9f34..60fd994 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/ChatScreenViewModel.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/viewmodel/ChatScreenViewModel.kt @@ -42,6 +42,8 @@ class ChatScreenViewModel( messages.clear() messages.addAll(initialMessages.distinctBy { it.id() }) loading = false + // Mark the room as read once messages are loaded + chatRepository.markAsRead(id) } } @@ -55,6 +57,7 @@ class ChatScreenViewModel( if (event.roomId() == id) { if (messages.none { it.id() == event.id() }) { messages.add(0, event) + chatRepository.markAsRead(id) } } else { newOtherMessages++