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
@@ -60,7 +60,6 @@ import androidx.compose.material3.rememberTooltipState
import androidx.compose.material3.toShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -87,27 +86,22 @@ import coop.composeapp.generated.resources.ic_new_chat
import coop.composeapp.generated.resources.ic_qr
import coop.composeapp.generated.resources.ic_request
import coop.composeapp.generated.resources.ic_scanner
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.PublicKey
import su.reya.coop.LocalAccountViewModel
import su.reya.coop.LocalAppViewModel
import su.reya.coop.LocalAuthViewModel
import su.reya.coop.LocalChatViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.LocalRelayViewModel
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalScanResult
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Room
import su.reya.coop.RoomKind
import su.reya.coop.Screen
import su.reya.coop.nostr.Room
import su.reya.coop.nostr.RoomKind
import su.reya.coop.nostr.ago
import su.reya.coop.ago
import su.reya.coop.rememberUiState
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, ExperimentalMaterial3Api::class)
@Composable
@@ -117,27 +111,24 @@ fun HomeScreen() {
val qrScanResult = LocalScanResult.current
val snackbarHostState = LocalSnackbarHostState.current
val clipboardManager = LocalClipboard.current
val appViewModel = LocalAppViewModel.current
val accountViewModel = LocalAccountViewModel.current
val nostrViewModel = LocalNostrViewModel.current
val chatViewModel = LocalChatViewModel.current
val profileViewModel = LocalProfileViewModel.current
val relayViewModel = LocalRelayViewModel.current
val authViewModel = LocalAuthViewModel.current
val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState(true)
val listState = rememberLazyListState()
val pullToRefreshState = rememberPullToRefreshState()
val currentUser = accountViewModel.nostr.signer.currentUser ?: return
val currentUserProfile = profileViewModel.getMetadata(currentUser)
val userProfile by currentUserProfile.collectAsStateWithLifecycle()
val userProfile by nostrViewModel.currentUserProfile.collectAsStateWithLifecycle()
val chatRooms by chatViewModel.chatRooms.collectAsStateWithLifecycle()
val isRelayListEmpty by relayViewModel.isRelayListEmpty.collectAsStateWithLifecycle()
val isRelayListEmpty by nostrViewModel.isRelayListEmpty.collectAsStateWithLifecycle()
val isSyncing by chatViewModel.isSyncing.collectAsStateWithLifecycle()
val isPartialProcessedGiftWrap by chatViewModel.isPartialProcessedGiftWrap.collectAsStateWithLifecycle()
val isBannerDismissed by appViewModel.isNotificationBannerDismissed.collectAsState()
val authState by authViewModel.state.collectAsStateWithLifecycle()
val isBannerDismissed = authState.isNotificationBannerDismissed
val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
var showBottomSheet by remember { mutableStateOf(false) }
@@ -165,7 +156,7 @@ fun HomeScreen() {
}
LaunchedEffect(Unit) {
chatViewModel.getChatRooms()
chatViewModel.refreshChatRooms()
}
LaunchedEffect(qrScanResult.content) {
@@ -218,8 +209,8 @@ fun HomeScreen() {
// User
IconButton(onClick = { showBottomSheet = true }) {
Avatar(
picture = userProfile?.asRecord()?.picture,
description = userProfile?.asRecord()?.displayName,
picture = userProfile?.picture,
description = userProfile?.name ?: "No name",
size = 32.dp,
)
}
@@ -289,7 +280,7 @@ fun HomeScreen() {
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
TextButton(
onClick = { appViewModel.dismissNotificationBanner() },
onClick = { authViewModel.dismissNotificationBanner() },
modifier = Modifier.weight(1f),
) {
Text(text = "Maybe later")
@@ -400,14 +391,6 @@ fun HomeScreen() {
onDismissRequest = { showBottomSheet = false },
sheetState = sheetState,
) {
val pubkey = accountViewModel.nostr.signer.currentUser
val shortPubkey = pubkey?.short() ?: "Not available"
val userName =
userProfile?.asRecord()?.displayName
?: userProfile?.asRecord()?.name
?: "No name"
val dismissAndRun: (suspend () -> Unit) -> Unit = { action ->
scope.launch {
sheetState.hide()
@@ -432,8 +415,8 @@ fun HomeScreen() {
contentAlignment = Alignment.Center
) {
Avatar(
picture = userProfile?.asRecord()?.picture,
description = userProfile?.asRecord()?.displayName,
picture = userProfile?.picture,
description = userProfile?.name ?: "No name",
shape = MaterialShapes.Cookie9Sided.toShape(),
modifier = Modifier.fillMaxSize()
)
@@ -443,7 +426,7 @@ fun HomeScreen() {
contentAlignment = Alignment.Center
) {
Text(
text = userName,
text = userProfile?.name ?: "No name",
style = MaterialTheme.typography.titleLargeEmphasized,
)
}
@@ -454,16 +437,15 @@ fun HomeScreen() {
OutlinedButton(
onClick = {
scope.launch {
pubkey?.let {
userProfile?.publicKey?.let {
val bech32 = it.toBech32()
val data =
ClipData.newPlainText(bech32, bech32)
val data = ClipData.newPlainText(bech32, bech32)
clipboardManager.setClipEntry(ClipEntry(data))
}
}
},
) {
Text(text = shortPubkey)
Text(text = userProfile?.shortPublicKey ?: "Unknown")
}
FilledIconButton(
onClick = {
@@ -487,7 +469,7 @@ fun HomeScreen() {
// Show the relay setup dialog if the msg relay list is empty
if (isRelayListEmpty) {
ModalBottomSheet(
onDismissRequest = { relayViewModel.dismissRelayWarning() },
onDismissRequest = { nostrViewModel.dismissRelayWarning() },
sheetState = sheetState,
containerColor = MaterialTheme.colorScheme.surfaceContainer,
) {
@@ -594,7 +576,7 @@ fun HomeScreen() {
scope.launch {
isBusy = true
try {
relayViewModel.refetchMsgRelays(currentUser)
nostrViewModel.refetchMsgRelays()
} catch (e: Exception) {
snackbarHostState.showSnackbar("Failed to refresh metadata: ${e.message}")
}
@@ -614,7 +596,7 @@ fun HomeScreen() {
enabled = !isBusy,
onClick = {
scope.launch {
relayViewModel.useDefaultMsgRelayList()
nostrViewModel.useDefaultMsgRelayList()
sheetState.hide()
}
},
@@ -637,34 +619,29 @@ fun HomeScreen() {
@Composable
fun NewRequests(requests: List<Room>) {
val navigator = LocalNavigator.current
val profileViewModel = LocalProfileViewModel.current
val nostrViewModel = LocalNostrViewModel.current
val total = requests.size
val firstRoom = requests.getOrNull(0)
val secondRoom = requests.getOrNull(1)
val firstName by remember(firstRoom) {
firstRoom?.nameFlow(profileViewModel) ?: flowOf("")
}.collectAsStateWithLifecycle("Loading...")
val secondName by remember(secondRoom) {
secondRoom?.nameFlow(profileViewModel) ?: flowOf("")
}.collectAsStateWithLifecycle("")
val firstRoomState by (firstRoom as Room).rememberUiState(nostrViewModel)
val secondRoomState by (secondRoom as Room).rememberUiState(nostrViewModel)
val supportingText = when {
total == 1 && firstRoom != null -> {
total == 1 -> {
val message = firstRoom.lastMessage ?: ""
"$firstName: $message"
"${firstRoomState.name}: $message"
}
total == 2 -> {
"$firstName and $secondName"
"${firstRoomState.name} and ${secondRoomState.name}"
}
total > 2 -> {
val othersCount = total - 2
val othersText = if (othersCount == 1) "1 other" else "$othersCount others"
"$firstName, $secondName and $othersText"
"${firstRoomState.name}, ${secondRoomState.name} and $othersText"
}
else -> ""
@@ -720,14 +697,13 @@ fun NewRequests(requests: List<Room>) {
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ChatRoom(room: Room, onClick: () -> Unit) {
val profileViewModel = LocalProfileViewModel.current
val displayName by remember(room) { room.nameFlow(profileViewModel) }.collectAsStateWithLifecycle("Loading...")
val picture by remember(room) { room.pictureFlow(profileViewModel) }.collectAsStateWithLifecycle(null)
val nostrViewModel = LocalNostrViewModel.current
val roomState by room.rememberUiState(nostrViewModel)
ListItem(
modifier = Modifier.clickable(onClick = onClick),
leadingContent = {
Avatar(picture = picture, description = displayName)
Avatar(picture = roomState.picture, description = roomState.picture)
},
headlineContent = {
Row(
@@ -735,7 +711,7 @@ fun ChatRoom(room: Room, onClick: () -> Unit) {
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = displayName,
text = roomState.name,
style = MaterialTheme.typography.titleMediumEmphasized,
modifier = Modifier.weight(1f)
)
@@ -768,7 +744,9 @@ fun BottomMenuList(
onDismiss: (suspend () -> Unit) -> Unit
) {
val navigator = LocalNavigator.current
val accountViewModel = LocalAccountViewModel.current
val nostrViewModel = LocalNostrViewModel.current
val chatViewModel = LocalChatViewModel.current
val authViewModel = LocalAuthViewModel.current
val defaultMenuList = listOf(
"Update Profile" to { navigator.navigate(Screen.UpdateProfile) },
@@ -798,7 +776,14 @@ fun BottomMenuList(
}
Spacer(modifier = Modifier.size(16.dp))
FilledTonalButton(
onClick = { onDismiss { accountViewModel.logout() } },
onClick = {
onDismiss {
authViewModel.logout(onLogout = {
nostrViewModel.resetInternalState()
chatViewModel.resetInternalState()
})
}
},
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError