chore: re-structure project (#31)

Reviewed-on: https://git.reya.su/reya/coop-mobile/pulls/31
This commit is contained in:
2026-06-29 13:42:56 +00:00
parent 3943e2baab
commit eed9d401da
33 changed files with 2289 additions and 2104 deletions
@@ -65,14 +65,16 @@ 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.LocalNostrViewModel
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Room
import su.reya.coop.Screen
import su.reya.coop.formatAsGroupHeader
import su.reya.coop.humanReadable
import su.reya.coop.roomId
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.shared.Avatar
import su.reya.coop.shared.getExpressiveFontFamily
import su.reya.coop.shared.nameFlow
@@ -83,10 +85,12 @@ import su.reya.coop.short
fun ChatScreen(id: Long, screening: Boolean = false) {
val snackbarHostState = LocalSnackbarHostState.current
val navigator = LocalNavigator.current
val viewModel = LocalNostrViewModel.current
val accountViewModel = LocalAccountViewModel.current
val chatViewModel = LocalChatViewModel.current
val profileViewModel = LocalProfileViewModel.current
// Get chat room by ID
val chatRooms by viewModel.chatRooms.collectAsStateWithLifecycle()
val chatRooms by chatViewModel.chatRooms.collectAsStateWithLifecycle()
val room by remember(id) { derivedStateOf { chatRooms.firstOrNull { it.id == id } } }
// Show empty screen
@@ -104,12 +108,14 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
return
}
val displayName by remember(room) {
room?.nameFlow(viewModel) ?: flowOf("Loading...")
val currentUser = accountViewModel.nostr.signer.currentUser
val displayName by remember(room, currentUser) {
room?.nameFlow(profileViewModel, currentUser) ?: flowOf("Loading...")
}.collectAsStateWithLifecycle("Loading...")
val picture by remember(room) {
room?.pictureFlow(viewModel) ?: flowOf(null)
room?.pictureFlow(profileViewModel) ?: flowOf(null)
}.collectAsStateWithLifecycle(null)
var text by remember { mutableStateOf("") }
@@ -129,7 +135,7 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
loading = true
// Get messages
val initialMessages = viewModel.getChatRoomMessages(id)
val initialMessages = chatViewModel.getChatRoomMessages(id)
messages.clear()
messages.addAll(initialMessages)
@@ -137,10 +143,10 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
loading = false
// Get msg relays for each member
viewModel.chatRoomConnect(id)
chatViewModel.chatRoomConnect(id)
// Handle new messages
viewModel.newEvents.collect { event ->
chatViewModel.newEvents.collect { event ->
if (event.roomId() == id) {
if (event.id() !in messages.map { it.id() }) {
messages.add(0, event)
@@ -314,7 +320,7 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
value = text,
onValueChange = { text = it },
onSend = {
viewModel.sendMessage(id, text)
chatViewModel.sendMessage(id, text)
text = ""
}
)
@@ -331,14 +337,14 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
fun ScreenerCard(room: Room) {
val pubkey = room.members.firstOrNull() ?: return
val viewModel = LocalNostrViewModel.current
val profileViewModel = LocalProfileViewModel.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) { viewModel.getMetadata(pubkey) }
val metadataFlow = remember(pubkey) { profileViewModel.getMetadata(pubkey) }
val metadata by metadataFlow.collectAsStateWithLifecycle()
val profile = metadata?.asRecord()
@@ -348,11 +354,11 @@ fun ScreenerCard(room: Room) {
LaunchedEffect(pubkey) {
scope.launch {
// Check contact
viewModel.verifyContact(pubkey).let { isContact = it }
profileViewModel.verifyContact(pubkey).let { isContact = it }
// Get mutual contacts
viewModel.mutualContacts(pubkey).let { mutualContacts = it }
profileViewModel.mutualContacts(pubkey).let { mutualContacts = it }
// Get the last activity
viewModel.verifyActivity(pubkey)?.let { lastActivity = it }
profileViewModel.verifyActivity(pubkey)?.let { lastActivity = it }
}
}
@@ -470,8 +476,9 @@ fun DateSeparator(date: String) {
fun ChatMessage(
rumor: UnsignedEvent
) {
val viewModel = LocalNostrViewModel.current
val currentUser = viewModel.currentUser()
val accountViewModel = LocalAccountViewModel.current
val chatViewModel = LocalChatViewModel.current
val currentUser = accountViewModel.nostr.signer.currentUser
val isMine = rumor.author() == currentUser
val bubbleShape = if (isMine) {
@@ -505,7 +512,7 @@ fun ChatMessage(
onClick = {
val id = rumor.id()
if (id != null) {
val sent = viewModel.isMessageSent(id)
val sent = chatViewModel.isMessageSent(id)
println("Sent: $sent")
}
}
@@ -55,7 +55,7 @@ import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.Nip05Address
import rust.nostr.sdk.PublicKey
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Screen
import su.reya.coop.shared.Avatar
@@ -66,9 +66,9 @@ import su.reya.coop.short
fun ContactListScreen() {
val navigator = LocalNavigator.current
val snackbarHostState = LocalSnackbarHostState.current
val viewModel = LocalNostrViewModel.current
val profileViewModel = LocalProfileViewModel.current
val contactList by viewModel.contactList.collectAsStateWithLifecycle()
val contactList by profileViewModel.contactList.collectAsStateWithLifecycle()
var openAddContactDialog by remember { mutableStateOf(false) }
Scaffold(
@@ -180,7 +180,7 @@ fun ContactListScreen() {
@Composable
fun AddContactDialog(onDismissRequest: () -> Unit) {
val snackbarHostState = LocalSnackbarHostState.current
val viewModel = LocalNostrViewModel.current
val profileViewModel = LocalProfileViewModel.current
val focusRequester = remember { FocusRequester() }
var contact by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }
@@ -225,7 +225,7 @@ fun AddContactDialog(onDismissRequest: () -> Unit) {
actions = {
IconButton(onClick = {
scope.launch {
val success = viewModel.addContact(contact)
val success = profileViewModel.addContact(contact)
if (success) onDismissRequest()
}
}) {
@@ -283,8 +283,8 @@ fun ContactListItem(
total: Int = 0,
onClick: () -> Unit,
) {
val viewModel = LocalNostrViewModel.current
val metadataFlow = remember(pubkey) { viewModel.getMetadata(pubkey) }
val profileViewModel = LocalProfileViewModel.current
val metadataFlow = remember(pubkey) { profileViewModel.getMetadata(pubkey) }
val metadata by metadataFlow.collectAsState(initial = null)
val profile = metadata?.asRecord()
@@ -293,7 +293,7 @@ fun ContactListItem(
SegmentedListItem(
onClick = onClick,
onLongClick = { viewModel.removeContact(pubkey) },
onLongClick = { profileViewModel.removeContact(pubkey) },
shapes = ListItemDefaults.segmentedShapes(
index = index,
count = total
@@ -91,14 +91,18 @@ 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.LocalChatViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.LocalRelayViewModel
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.ago
import su.reya.coop.nostr.Room
import su.reya.coop.nostr.RoomKind
import su.reya.coop.nostr.ago
import su.reya.coop.shared.Avatar
import su.reya.coop.shared.getExpressiveFontFamily
import su.reya.coop.shared.nameFlow
@@ -113,7 +117,11 @@ fun HomeScreen() {
val qrScanResult = LocalScanResult.current
val snackbarHostState = LocalSnackbarHostState.current
val clipboardManager = LocalClipboard.current
val viewModel = LocalNostrViewModel.current
val appViewModel = LocalAppViewModel.current
val accountViewModel = LocalAccountViewModel.current
val chatViewModel = LocalChatViewModel.current
val profileViewModel = LocalProfileViewModel.current
val relayViewModel = LocalRelayViewModel.current
val scope = rememberCoroutineScope()
@@ -121,15 +129,15 @@ fun HomeScreen() {
val listState = rememberLazyListState()
val pullToRefreshState = rememberPullToRefreshState()
val currentUser = viewModel.currentUser() ?: return
val currentUserProfile = viewModel.getMetadata(currentUser)
val currentUser = accountViewModel.nostr.signer.currentUser ?: return
val currentUserProfile = profileViewModel.getMetadata(currentUser)
val userProfile by currentUserProfile.collectAsStateWithLifecycle()
val chatRooms by viewModel.chatRooms.collectAsStateWithLifecycle()
val isRelayListEmpty by viewModel.isRelayListEmpty.collectAsStateWithLifecycle()
val isSyncing by viewModel.isSyncing.collectAsStateWithLifecycle()
val isPartialProcessedGiftWrap by viewModel.isPartialProcessedGiftWrap.collectAsStateWithLifecycle()
val isBannerDismissed by viewModel.isNotificationBannerDismissed.collectAsState()
val chatRooms by chatViewModel.chatRooms.collectAsStateWithLifecycle()
val isRelayListEmpty by relayViewModel.isRelayListEmpty.collectAsStateWithLifecycle()
val isSyncing by chatViewModel.isSyncing.collectAsStateWithLifecycle()
val isPartialProcessedGiftWrap by chatViewModel.isPartialProcessedGiftWrap.collectAsStateWithLifecycle()
val isBannerDismissed by appViewModel.isNotificationBannerDismissed.collectAsState()
val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
var showBottomSheet by remember { mutableStateOf(false) }
@@ -157,7 +165,7 @@ fun HomeScreen() {
}
LaunchedEffect(Unit) {
viewModel.getChatRooms()
chatViewModel.getChatRooms()
}
LaunchedEffect(qrScanResult.content) {
@@ -165,7 +173,7 @@ fun HomeScreen() {
runCatching { PublicKey.parse(result) }
.onSuccess { pubkey ->
try {
val roomId = viewModel.createChatRoom(listOf(pubkey))
val roomId = chatViewModel.createChatRoom(listOf(pubkey))
navigator.navigate(Screen.Chat(roomId))
} catch (e: Exception) {
e.message?.let { snackbarHostState.showSnackbar(it) }
@@ -281,7 +289,7 @@ fun HomeScreen() {
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
TextButton(
onClick = { viewModel.dismissNotificationBanner() },
onClick = { appViewModel.dismissNotificationBanner() },
modifier = Modifier.weight(1f),
) {
Text(text = "Maybe later")
@@ -322,7 +330,7 @@ fun HomeScreen() {
onRefresh = {
scope.launch {
isRefreshing = true
viewModel.refreshChatRooms()
chatViewModel.refreshChatRooms()
isRefreshing = false
}
},
@@ -392,7 +400,7 @@ fun HomeScreen() {
onDismissRequest = { showBottomSheet = false },
sheetState = sheetState,
) {
val pubkey = viewModel.currentUser()
val pubkey = accountViewModel.nostr.signer.currentUser
val shortPubkey = pubkey?.short() ?: "Not available"
val userName =
@@ -479,7 +487,7 @@ fun HomeScreen() {
// Show the relay setup dialog if the msg relay list is empty
if (isRelayListEmpty) {
ModalBottomSheet(
onDismissRequest = { viewModel.dismissRelayWarning() },
onDismissRequest = { relayViewModel.dismissRelayWarning() },
sheetState = sheetState,
containerColor = MaterialTheme.colorScheme.surfaceContainer,
) {
@@ -586,7 +594,7 @@ fun HomeScreen() {
scope.launch {
isBusy = true
try {
viewModel.refetchMsgRelays(currentUser)
relayViewModel.refetchMsgRelays(currentUser)
} catch (e: Exception) {
snackbarHostState.showSnackbar("Failed to refresh metadata: ${e.message}")
}
@@ -606,7 +614,7 @@ fun HomeScreen() {
enabled = !isBusy,
onClick = {
scope.launch {
viewModel.useDefaultMsgRelayList()
relayViewModel.useDefaultMsgRelayList()
sheetState.hide()
}
},
@@ -629,18 +637,18 @@ fun HomeScreen() {
@Composable
fun NewRequests(requests: List<Room>) {
val navigator = LocalNavigator.current
val viewModel = LocalNostrViewModel.current
val profileViewModel = LocalProfileViewModel.current
val total = requests.size
val firstRoom = requests.getOrNull(0)
val secondRoom = requests.getOrNull(1)
val firstName by remember(firstRoom) {
firstRoom?.nameFlow(viewModel) ?: flowOf("")
firstRoom?.nameFlow(profileViewModel) ?: flowOf("")
}.collectAsStateWithLifecycle("Loading...")
val secondName by remember(secondRoom) {
secondRoom?.nameFlow(viewModel) ?: flowOf("")
secondRoom?.nameFlow(profileViewModel) ?: flowOf("")
}.collectAsStateWithLifecycle("")
val supportingText = when {
@@ -712,9 +720,9 @@ fun NewRequests(requests: List<Room>) {
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ChatRoom(room: Room, onClick: () -> Unit) {
val viewModel = LocalNostrViewModel.current
val displayName by remember(room) { room.nameFlow(viewModel) }.collectAsStateWithLifecycle("Loading...")
val picture by remember(room) { room.pictureFlow(viewModel) }.collectAsStateWithLifecycle(null)
val profileViewModel = LocalProfileViewModel.current
val displayName by remember(room) { room.nameFlow(profileViewModel) }.collectAsStateWithLifecycle("Loading...")
val picture by remember(room) { room.pictureFlow(profileViewModel) }.collectAsStateWithLifecycle(null)
ListItem(
modifier = Modifier.clickable(onClick = onClick),
@@ -760,7 +768,7 @@ fun BottomMenuList(
onDismiss: (suspend () -> Unit) -> Unit
) {
val navigator = LocalNavigator.current
val viewModel = LocalNostrViewModel.current
val accountViewModel = LocalAccountViewModel.current
val defaultMenuList = listOf(
"Update Profile" to { navigator.navigate(Screen.UpdateProfile) },
@@ -790,7 +798,7 @@ fun BottomMenuList(
}
Spacer(modifier = Modifier.size(16.dp))
FilledTonalButton(
onClick = { onDismiss { viewModel.logout() } },
onClick = { onDismiss { accountViewModel.logout() } },
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError
@@ -58,8 +58,10 @@ import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.Keys
import rust.nostr.sdk.NostrConnectUri
import rust.nostr.sdk.PublicKey
import su.reya.coop.LocalAccountViewModel
import su.reya.coop.LocalAppViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.LocalScanResult
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Screen
@@ -74,17 +76,19 @@ fun ImportScreen() {
val navigator = LocalNavigator.current
val qrScanResult = LocalScanResult.current
val focusManager = LocalFocusManager.current
val viewModel = LocalNostrViewModel.current
val appViewModel = LocalAppViewModel.current
val accountViewModel = LocalAccountViewModel.current
val profileViewModel = LocalProfileViewModel.current
val scope = rememberCoroutineScope()
val isBusy by viewModel.isBusy.collectAsStateWithLifecycle(false)
val isBusy by appViewModel.isBusy.collectAsStateWithLifecycle(false)
var secret by remember { mutableStateOf("") }
var pubkey by remember { mutableStateOf<PublicKey?>(null) }
// Get metadata when pubkey changes
val metadata by remember(pubkey) {
pubkey?.let(viewModel::getMetadata) ?: flowOf(null)
pubkey?.let(profileViewModel::getMetadata) ?: flowOf(null)
}.collectAsStateWithLifecycle(null)
val profile = metadata?.asRecord()
@@ -246,10 +250,10 @@ fun ImportScreen() {
onClick = {
scope.launch {
if (pubkey == null) {
viewModel.verifyIdentity(secret).let { pubkey = it }
accountViewModel.verifyIdentity(secret).let { pubkey = it }
} else {
// Import the identity
viewModel.importIdentity(secret)
accountViewModel.importIdentity(secret)
// Navigate to the home screen
navigator.navigate(Screen.Home)
}
@@ -19,16 +19,16 @@ import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.ic_arrow_back
import io.github.alexzhirkevich.qrose.rememberQrCodePainter
import org.jetbrains.compose.resources.painterResource
import su.reya.coop.LocalAccountViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalSnackbarHostState
@Composable
fun MyQrScreen() {
val navigator = LocalNavigator.current
val snackbarHostState = LocalSnackbarHostState.current
val viewModel = LocalNostrViewModel.current
val currentUser = viewModel.currentUser() ?: return
val accountViewModel = LocalAccountViewModel.current
val currentUser = accountViewModel.nostr.signer.currentUser ?: return
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
@@ -46,6 +46,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.ic_arrow_back
import coop.composeapp.generated.resources.ic_arrow_next
@@ -54,8 +55,9 @@ import coop.composeapp.generated.resources.ic_scanner
import kotlinx.coroutines.delay
import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.PublicKey
import su.reya.coop.LocalChatViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.LocalScanResult
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Screen
@@ -69,13 +71,15 @@ fun NewChatScreen() {
val snackbarHostState = LocalSnackbarHostState.current
val navigator = LocalNavigator.current
val qrScanResult = LocalScanResult.current
val viewModel = LocalNostrViewModel.current
val chatViewModel = LocalChatViewModel.current
val profileViewModel = LocalProfileViewModel.current
val contactList by profileViewModel.contactList.collectAsStateWithLifecycle()
var query by remember { mutableStateOf("") }
val contactList by viewModel.contactList.collectAsState(initial = emptySet())
val createGroup = remember { mutableStateOf(false) }
val searchResults = remember { mutableStateListOf<PublicKey>() }
val selectedReceivers = remember { mutableStateListOf<PublicKey>() }
var query by remember { mutableStateOf("") }
LaunchedEffect(query) {
if (query.length >= 3) {
@@ -92,12 +96,12 @@ fun NewChatScreen() {
selectedReceivers.add(pubkey)
}
} else if (query.contains("@")) {
val pubkey = viewModel.searchByAddress(query)
val pubkey = profileViewModel.searchByAddress(query)
if (pubkey != null) {
selectedReceivers.add(pubkey)
}
} else {
val results = viewModel.searchByNostr(query)
val results = profileViewModel.searchByNostr(query)
searchResults.clear()
searchResults.addAll(results)
}
@@ -167,7 +171,7 @@ fun NewChatScreen() {
) {
ExtendedFloatingActionButton(
onClick = {
val roomId = viewModel.createChatRoom(selectedReceivers.toList())
val roomId = chatViewModel.createChatRoom(selectedReceivers.toList())
navigator.navigate(Screen.Chat(roomId))
},
expanded = false,
@@ -258,7 +262,7 @@ fun NewChatScreen() {
items = searchResults,
selectedReceivers = selectedReceivers,
onContactClick = { pubkey ->
val roomId = viewModel.createChatRoom(listOf(pubkey))
val roomId = chatViewModel.createChatRoom(listOf(pubkey))
navigator.navigate(Screen.Chat(roomId))
},
)
@@ -269,7 +273,7 @@ fun NewChatScreen() {
items = contactList.toList(),
selectedReceivers = selectedReceivers,
onContactClick = { pubkey ->
val roomId = viewModel.createChatRoom(listOf(pubkey))
val roomId = chatViewModel.createChatRoom(listOf(pubkey))
navigator.navigate(Screen.Chat(roomId))
}
)
@@ -286,8 +290,8 @@ fun ReceiverChip(
pubkey: PublicKey,
onRemove: () -> Unit
) {
val viewModel = LocalNostrViewModel.current
val metadataFlow = remember(pubkey) { viewModel.getMetadata(pubkey) }
val profileViewModel = LocalProfileViewModel.current
val metadataFlow = remember(pubkey) { profileViewModel.getMetadata(pubkey) }
val metadata by metadataFlow.collectAsState(initial = null)
val profile = metadata?.asRecord()
@@ -372,8 +376,8 @@ fun ContactListItem(
onClick: () -> Unit,
onLongClick: () -> Unit
) {
val viewModel = LocalNostrViewModel.current
val metadataFlow = remember(pubkey) { viewModel.getMetadata(pubkey) }
val profileViewModel = LocalProfileViewModel.current
val metadataFlow = remember(pubkey) { profileViewModel.getMetadata(pubkey) }
val metadata by metadataFlow.collectAsState(initial = null)
val profile = metadata?.asRecord()
@@ -5,17 +5,21 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch
import su.reya.coop.LocalAccountViewModel
import su.reya.coop.LocalAppViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.Screen
import su.reya.coop.shared.ProfileEditor
@Composable
fun NewIdentityScreen() {
val viewModel = LocalNostrViewModel.current
val appViewModel = LocalAppViewModel.current
val accountViewModel = LocalAccountViewModel.current
val profileViewModel = LocalProfileViewModel.current
val navigator = LocalNavigator.current
val scope = rememberCoroutineScope()
val isBusy by viewModel.isBusy.collectAsStateWithLifecycle(false)
val isBusy by appViewModel.isBusy.collectAsStateWithLifecycle(false)
ProfileEditor(
title = "Create a new identity",
@@ -24,7 +28,7 @@ fun NewIdentityScreen() {
onBack = { navigator.goBack() },
onConfirm = { name, bio, bytes, type ->
scope.launch {
viewModel.createIdentity(name, bio, bytes, type)
accountViewModel.createIdentity(name, bio, bytes, type, profileViewModel)
navigator.navigate(Screen.Home)
}
}
@@ -45,8 +45,8 @@ import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.coop
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource
import su.reya.coop.LocalAccountViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Screen
import su.reya.coop.shared.getExpressiveFontFamily
@@ -57,7 +57,7 @@ fun OnboardingScreen() {
val context = LocalContext.current
val snackbarHostState = LocalSnackbarHostState.current
val navigator = LocalNavigator.current
val viewModel = LocalNostrViewModel.current
val accountViewModel = LocalAccountViewModel.current
val scope = rememberCoroutineScope()
val logoPainter = painterResource(Res.drawable.coop)
@@ -158,9 +158,9 @@ fun OnboardingScreen() {
FilledTonalButton(
onClick = {
scope.launch {
if (viewModel.isExternalSignerAvailable()) {
if (accountViewModel.isExternalSignerAvailable()) {
try {
viewModel.connectExternalSigner()
accountViewModel.connectExternalSigner()
navigator.navigate(Screen.Home)
} catch (e: Exception) {
e.message?.let { snackbarHostState.showSnackbar(it) }
@@ -44,8 +44,9 @@ import coop.composeapp.generated.resources.ic_share
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.PublicKey
import su.reya.coop.LocalChatViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Screen
import su.reya.coop.shared.Avatar
@@ -58,12 +59,13 @@ fun ProfileScreen(pubkey: String) {
val context = LocalContext.current
val snackbarHostState = LocalSnackbarHostState.current
val navigator = LocalNavigator.current
val viewModel = LocalNostrViewModel.current
val chatViewModel = LocalChatViewModel.current
val profileViewModel = LocalProfileViewModel.current
val scope = rememberCoroutineScope()
val pubkey = runCatching { PublicKey.parse(pubkey) }.getOrNull() ?: return
val metadataFlow = remember(pubkey) { viewModel.getMetadata(pubkey) }
val metadataFlow = remember(pubkey) { profileViewModel.getMetadata(pubkey) }
val metadata by metadataFlow.collectAsStateWithLifecycle()
val profile = metadata?.asRecord()
@@ -159,7 +161,7 @@ fun ProfileScreen(pubkey: String) {
onClick = {
scope.launch {
try {
val roomId = viewModel.createChatRoom(listOf(pubkey))
val roomId = chatViewModel.createChatRoom(listOf(pubkey))
navigator.navigate(Screen.Chat(roomId))
} catch (e: Exception) {
e.message?.let { snackbarHostState.showSnackbar(it) }
@@ -66,7 +66,7 @@ import org.jetbrains.compose.resources.painterResource
import rust.nostr.sdk.RelayMetadata
import rust.nostr.sdk.RelayUrl
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalRelayViewModel
import su.reya.coop.LocalSnackbarHostState
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -74,7 +74,7 @@ import su.reya.coop.LocalSnackbarHostState
fun RelayScreen() {
val navigator = LocalNavigator.current
val snackbarHostState = LocalSnackbarHostState.current
val viewModel = LocalNostrViewModel.current
val relayViewModel = LocalRelayViewModel.current
val scope = rememberCoroutineScope()
val msgRelayList = remember { mutableStateListOf<RelayUrl>() }
@@ -96,8 +96,8 @@ fun RelayScreen() {
var relayToDelete by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) {
relayList.putAll(viewModel.currentUserRelayList())
msgRelayList.addAll(viewModel.currentUserMsgRelayList())
relayList.putAll(relayViewModel.currentUserRelayList())
msgRelayList.addAll(relayViewModel.currentUserMsgRelayList())
}
Scaffold(
@@ -321,11 +321,11 @@ fun RelayScreen() {
return@launch
}
try {
viewModel.removeMsgRelay(relayToDelete!!)
relayViewModel.removeMsgRelay(relayToDelete!!)
msgRelayList.removeIf { it.toString() == relayToDelete }
relayToDelete = null
} catch (e: Exception) {
snackbarHostState.showSnackbar("Failed to remove relay")
snackbarHostState.showSnackbar("Failed to remove relay: ${e.message}")
}
}
}
@@ -349,7 +349,7 @@ fun AddRelayDialog(
onMsgRelayAdded: (newRelay: String) -> Unit,
onRelayAdded: (newRelay: String, metadata: RelayMetadata?) -> Unit,
) {
val viewModel = LocalNostrViewModel.current
val relayViewModel = LocalRelayViewModel.current
val snackbarHostState = LocalSnackbarHostState.current
val scope = rememberCoroutineScope()
@@ -401,17 +401,17 @@ fun AddRelayDialog(
if (!isError) {
when (selected) {
"Messaging" -> {
viewModel.addMsgRelay(relayAddress)
relayViewModel.addMsgRelay(relayAddress)
onMsgRelayAdded(relayAddress)
}
"Inbox" -> {
viewModel.addInboxRelay(relayAddress)
relayViewModel.addInboxRelay(relayAddress)
onRelayAdded(relayAddress, RelayMetadata.WRITE)
}
"Outbox" -> {
viewModel.addOutboxRelay(relayAddress)
relayViewModel.addOutboxRelay(relayAddress)
onRelayAdded(relayAddress, RelayMetadata.READ)
}
}
@@ -37,25 +37,25 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.ic_arrow_back
import kotlinx.coroutines.launch
import su.reya.coop.LocalChatViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.RoomKind
import su.reya.coop.Screen
import su.reya.coop.nostr.RoomKind
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
fun RequestListScreen() {
val navigator = LocalNavigator.current
val snackbarHostState = LocalSnackbarHostState.current
val viewModel = LocalNostrViewModel.current
val chatViewModel = LocalChatViewModel.current
val scope = rememberCoroutineScope()
val listState = rememberLazyListState()
val pullToRefreshState = rememberPullToRefreshState()
var isRefreshing by remember { mutableStateOf(false) }
val chatRooms by viewModel.chatRooms.collectAsStateWithLifecycle()
val chatRooms by chatViewModel.chatRooms.collectAsStateWithLifecycle()
// Get all request rooms
val requests = remember(chatRooms) {
@@ -103,7 +103,7 @@ fun RequestListScreen() {
onRefresh = {
scope.launch {
isRefreshing = true
viewModel.refreshChatRooms()
chatViewModel.refreshChatRooms()
isRefreshing = false
}
},
@@ -1,24 +1,27 @@
package su.reya.coop.screens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch
import su.reya.coop.LocalAccountViewModel
import su.reya.coop.LocalAppViewModel
import su.reya.coop.LocalNavigator
import su.reya.coop.LocalNostrViewModel
import su.reya.coop.LocalProfileViewModel
import su.reya.coop.shared.ProfileEditor
@Composable
fun UpdateProfileScreen() {
val viewModel = LocalNostrViewModel.current
val appViewModel = LocalAppViewModel.current
val accountViewModel = LocalAccountViewModel.current
val profileViewModel = LocalProfileViewModel.current
val navigator = LocalNavigator.current
val scope = rememberCoroutineScope()
val currentUser = viewModel.currentUser() ?: return
val metadata by viewModel.getMetadata(currentUser).collectAsState(initial = null)
val isBusy by viewModel.isBusy.collectAsStateWithLifecycle(false)
val currentUser = accountViewModel.nostr.signer.currentUser ?: return
val metadata by profileViewModel.getMetadata(currentUser).collectAsStateWithLifecycle()
val isBusy by appViewModel.isBusy.collectAsStateWithLifecycle(false)
val profile = metadata?.asRecord()
@@ -32,7 +35,7 @@ fun UpdateProfileScreen() {
onBack = { navigator.goBack() },
onConfirm = { name, bio, bytes, type ->
scope.launch {
viewModel.updateProfile(name, bio, bytes, type)
profileViewModel.updateProfile(name, bio, bytes, type)
navigator.goBack()
}
}