chore: refactor the internal structure (#33)

Reviewed-on: https://git.reya.su/reya/coop-mobile/pulls/33
This commit is contained in:
2026-07-03 01:20:36 +00:00
parent cd5a393a01
commit a2d28ecc40
36 changed files with 1211 additions and 1139 deletions
@@ -70,27 +70,24 @@ import coop.composeapp.generated.resources.ic_cancel
import coop.composeapp.generated.resources.ic_check_circle
import coop.composeapp.generated.resources.ic_send
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.Timestamp
import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.LocalAccountViewModel
import su.reya.coop.LocalChatViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Room
import su.reya.coop.Screen
import su.reya.coop.nostr.Room
import su.reya.coop.nostr.formatAsGroupHeader
import su.reya.coop.nostr.humanReadable
import su.reya.coop.nostr.roomId
import su.reya.coop.formatAsGroupHeader
import su.reya.coop.humanReadable
import su.reya.coop.rememberUiState
import su.reya.coop.roomId
import su.reya.coop.shared.Avatar
import su.reya.coop.shared.getExpressiveFontFamily
import su.reya.coop.shared.nameFlow
import su.reya.coop.shared.pictureFlow
import su.reya.coop.short
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -99,11 +96,14 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
val context = LocalContext.current
val snackbarHostState = LocalSnackbarHostState.current
val navigator = LocalNavigator.current
val nostrViewModel = LocalNostrViewModel.current
val chatViewModel = LocalChatViewModel.current
val profileViewModel = LocalProfileViewModel.current
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
// Get current user
val currentUser by nostrViewModel.currentUserProfile.collectAsStateWithLifecycle()
// Get chat room by ID
val chatRooms by chatViewModel.chatRooms.collectAsStateWithLifecycle()
val room by remember(id) { derivedStateOf { chatRooms.firstOrNull { it.id == id } } }
@@ -123,15 +123,7 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
return
}
val displayName by remember(room) {
room?.nameFlow(profileViewModel) ?: flowOf("Loading...")
}.collectAsStateWithLifecycle("Loading...")
val picture by remember(room) {
room?.pictureFlow(profileViewModel) ?: flowOf(null)
}.collectAsStateWithLifecycle(null)
val roomState by (room as Room).rememberUiState(nostrViewModel, currentUser?.publicKey)
var text by remember { mutableStateOf("") }
var loading by remember { mutableStateOf(true) }
var newOtherMessages by remember { mutableIntStateOf(0) }
@@ -215,14 +207,14 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
LoadingIndicator(modifier = Modifier.size(32.dp))
} else {
Avatar(
picture = picture,
description = displayName,
picture = roomState.picture,
description = roomState.name,
size = 32.dp,
)
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = displayName,
text = roomState.name,
style = MaterialTheme.typography.titleMediumEmphasized,
)
}
@@ -282,7 +274,10 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
items = messagesInGroup,
key = { it.id()?.toBech32() ?: it.hashCode() }
) {
ChatMessage(it)
ChatMessage(
rumor = it,
isMine = currentUser?.publicKey == it.author()
)
}
item {
DateSeparator(dateHeader)
@@ -377,28 +372,24 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
fun ScreenerCard(room: Room) {
val pubkey = room.members.firstOrNull() ?: return
val profileViewModel = LocalProfileViewModel.current
val nostrViewModel = LocalNostrViewModel.current
val scope = rememberCoroutineScope()
var isContact by remember { mutableStateOf(false) }
var mutualContacts by remember { mutableStateOf<Set<PublicKey>>(emptySet()) }
var lastActivity by remember { mutableStateOf<Timestamp?>(null) }
val metadataFlow = remember(pubkey) { profileViewModel.getMetadata(pubkey) }
val metadata by metadataFlow.collectAsStateWithLifecycle()
val profile = metadata?.asRecord()
val displayName = profile?.displayName ?: profile?.name ?: "No name"
val picture = profile?.picture
val profileFlow = remember(pubkey) { nostrViewModel.getMetadata(pubkey) }
val profile by profileFlow.collectAsStateWithLifecycle()
LaunchedEffect(pubkey) {
scope.launch {
// Check contact
profileViewModel.verifyContact(pubkey).let { isContact = it }
nostrViewModel.verifyContact(pubkey).let { isContact = it }
// Get mutual contacts
profileViewModel.mutualContacts(pubkey).let { mutualContacts = it }
nostrViewModel.mutualContacts(pubkey).let { mutualContacts = it }
// Get the last activity
profileViewModel.verifyActivity(pubkey)?.let { lastActivity = it }
nostrViewModel.verifyActivity(pubkey)?.let { lastActivity = it }
}
}
@@ -415,7 +406,7 @@ fun ScreenerCard(room: Room) {
horizontalAlignment = Alignment.CenterHorizontally,
) {
Avatar(
picture = picture,
picture = profile?.picture,
description = "Profile picture",
modifier = Modifier.size(120.dp),
shape = MaterialShapes.Cookie12Sided.toShape(),
@@ -425,7 +416,7 @@ fun ScreenerCard(room: Room) {
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = displayName,
text = profile?.name ?: "No name",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleLargeEmphasized.copy(
fontFamily = getExpressiveFontFamily()
@@ -514,13 +505,9 @@ fun DateSeparator(date: String) {
@Composable
fun ChatMessage(
rumor: UnsignedEvent
rumor: UnsignedEvent,
isMine: Boolean = false,
) {
val accountViewModel = LocalAccountViewModel.current
val chatViewModel = LocalChatViewModel.current
val currentUser = accountViewModel.nostr.signer.currentUser
val isMine = rumor.author() == currentUser
val bubbleShape = if (isMine) {
RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp, bottomStart = 20.dp, bottomEnd = 4.dp)
} else {
@@ -546,17 +533,7 @@ fun ChatMessage(
color = containerColor,
contentColor = contentColor,
shape = bubbleShape,
modifier = Modifier
.widthIn(max = 280.dp)
.clickable(
onClick = {
val id = rumor.id()
if (id != null) {
val sent = chatViewModel.isMessageSent(id)
println("Sent: $sent")
}
}
)
modifier = Modifier.widthIn(max = 280.dp)
) {
Text(
text = rumor.content(),