clean up chat screen

This commit is contained in:
2026-07-15 07:32:39 +07:00
parent 1485a767ed
commit 9c9d9ae882
4 changed files with 57 additions and 53 deletions

View File

@@ -6,11 +6,14 @@ import android.os.Build
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.material3.MotionScheme
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.Typography
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
@@ -24,6 +27,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.util.Consumer
import androidx.lifecycle.ViewModel
@@ -230,12 +235,14 @@ fun App(
NewIdentityScreen(accountViewModel)
}
entry<Screen.Chat> { key ->
val factory = remember(key) {
val initialRoom = remember(key.id) { chatRepository.getChatRoom(key.id) }
if (initialRoom != null) {
val factory = remember(initialRoom) {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return ChatScreenViewModel(
key.id,
initialRoom,
key.screening,
accountRepository,
chatRepository
@@ -250,6 +257,12 @@ fun App(
),
accountViewModel
)
} else {
// Handle the rare case where the room isn't in DB (e.g., invalid deep link)
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("Room not found")
}
}
}
entry<Screen.NewChat> {
NewChatScreen(accountViewModel, chatViewModel)

View File

@@ -94,12 +94,11 @@ import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalProfileCache
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Room
import su.reya.coop.RoomUiState
import su.reya.coop.Screen
import su.reya.coop.flow
import su.reya.coop.formatAsGroup
import su.reya.coop.shared.Avatar
import su.reya.coop.uiStateFlow
import su.reya.coop.viewmodel.AccountViewModel
import su.reya.coop.viewmodel.ChatScreenViewModel
@@ -118,25 +117,11 @@ fun ChatScreen(
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
val id = viewModel.id
val currentUser by viewModel.currentUser.collectAsStateWithLifecycle()
val chatRooms by viewModel.chatRooms.collectAsStateWithLifecycle()
val room by remember(id) { derivedStateOf { chatRooms.firstOrNull { it.id == id } } }
val pubkey = currentUser?.publicKey
// Show empty screen
if (room == null) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "Something went wrong.",
style = MaterialTheme.typography.titleMediumEmphasized,
color = MaterialTheme.colorScheme.onSurface
)
}
return
}
val room by viewModel.room.collectAsStateWithLifecycle()
val roomState by room.flow(profileCache, pubkey).collectAsStateWithLifecycle(RoomUiState())
val loading = viewModel.loading
val newOtherMessages = viewModel.newOtherMessages
@@ -146,9 +131,6 @@ fun ChatScreen(
val groupedMessages =
remember { derivedStateOf { messages.groupBy { it.createdAt().formatAsGroup() } } }
val roomState by (room as Room).uiStateFlow(profileCache, currentUser?.publicKey)
.collectAsStateWithLifecycle(RoomUiState())
var text by remember { mutableStateOf("") }
var selectedMessage by remember { mutableStateOf<Pair<MessageModel, Rect>?>(null) }
var replyingTo by remember { mutableStateOf<MessageModel?>(null) }
@@ -193,9 +175,7 @@ fun ChatScreen(
}
}
Box(
modifier = Modifier.fillMaxSize()
) {
Box(modifier = Modifier.fillMaxSize()) {
Scaffold(
modifier = Modifier.blur(blurAmount),
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.union(WindowInsets.ime),
@@ -207,7 +187,7 @@ fun ChatScreen(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
room?.members?.firstOrNull()?.let { pubkey ->
room.members.firstOrNull()?.let { pubkey ->
navigator.navigate(Screen.Profile(pubkey.toBech32()))
}
}
@@ -265,7 +245,7 @@ fun ChatScreen(
.padding(bottom = innerPadding.calculateBottomPadding())
) {
if (requireScreening) {
room?.let { ScreenerCard(accountViewModel, it) }
ScreenerCard(accountViewModel, room)
}
when (messages.isNotEmpty()) {
@@ -283,8 +263,7 @@ fun ChatScreen(
items = messagesInGroup,
key = { it.ensureId().id()?.toHex()!! }
) { event ->
val model =
rememberMessageModel(event, currentUser?.publicKey)
val model = rememberMessageModel(event, pubkey)
val replyPreview =
remember(model.replyEventIds, messages.size) {
@@ -298,7 +277,9 @@ fun ChatScreen(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
replyPreview?.let { ReplyPreview(it, model.isMine) }
replyPreview?.let {
ReplyPreview(it, model.isMine)
}
ChatMessage(
model = model,
modifier = Modifier.graphicsLayer {

View File

@@ -71,7 +71,7 @@ data class RoomUiState(
val isGroup: Boolean = false
)
fun Room.uiStateFlow(
fun Room.flow(
profileCache: ProfileCache,
currentUser: PublicKey? = null
): Flow<RoomUiState> {

View File

@@ -7,30 +7,40 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import rust.nostr.sdk.EventId
import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.Profile
import su.reya.coop.Room
import su.reya.coop.repository.AccountRepository
import su.reya.coop.repository.ChatRepository
import su.reya.coop.roomId
class ChatScreenViewModel(
val id: Long,
initialRoom: Room,
screening: Boolean,
accountRepository: AccountRepository,
private val chatRepository: ChatRepository,
) : ViewModel(), ErrorHost by chatRepository {
val currentUser: StateFlow<Profile?> = accountRepository.currentUserProfile
val chatRooms: StateFlow<List<Room>> = chatRepository.chatRooms
var loading by mutableStateOf(true)
var newOtherMessages by mutableIntStateOf(0)
var requireScreening by mutableStateOf(screening)
val messages = mutableStateListOf<UnsignedEvent>()
val currentUser = accountRepository.currentUserProfile
val id = initialRoom.id
val room: StateFlow<Room> = chatRepository.chatRooms
.map { rooms -> rooms.find { it.id == id } ?: initialRoom }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = initialRoom
)
init {
loadMessages()
connect()