update relay warning

This commit is contained in:
2026-06-11 08:45:58 +07:00
parent f7d2866517
commit 1a7747ea08
3 changed files with 139 additions and 61 deletions

View File

@@ -82,6 +82,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coop.composeapp.generated.resources.Res import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.ic_close
import coop.composeapp.generated.resources.ic_new_chat import coop.composeapp.generated.resources.ic_new_chat
import coop.composeapp.generated.resources.ic_qr import coop.composeapp.generated.resources.ic_qr
import coop.composeapp.generated.resources.ic_scanner import coop.composeapp.generated.resources.ic_scanner
@@ -97,6 +98,7 @@ import su.reya.coop.Screen
import su.reya.coop.ago import su.reya.coop.ago
import su.reya.coop.shared.Avatar import su.reya.coop.shared.Avatar
import su.reya.coop.shared.displayNameFlow import su.reya.coop.shared.displayNameFlow
import su.reya.coop.shared.getExpressiveFontFamily
import su.reya.coop.shared.pictureFlow import su.reya.coop.shared.pictureFlow
import su.reya.coop.short import su.reya.coop.short
@@ -120,13 +122,14 @@ fun HomeScreen() {
val isBannerDismissed by viewModel.isNotificationBannerDismissed.collectAsState() val isBannerDismissed by viewModel.isNotificationBannerDismissed.collectAsState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val sheetState = rememberModalBottomSheetState() val sheetState = rememberModalBottomSheetState(true)
val listState = rememberLazyListState() val listState = rememberLazyListState()
val pullToRefreshState = rememberPullToRefreshState() val pullToRefreshState = rememberPullToRefreshState()
val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } } val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
var showBottomSheet by remember { mutableStateOf(false) } var showBottomSheet by remember { mutableStateOf(false) }
var isRefreshing by remember { mutableStateOf(false) } var isRefreshing by remember { mutableStateOf(false) }
var isBusy by remember { mutableStateOf(false) }
var isNotificationEnabled by remember { var isNotificationEnabled by remember {
mutableStateOf(NotificationManagerCompat.from(context).areNotificationsEnabled()) mutableStateOf(NotificationManagerCompat.from(context).areNotificationsEnabled())
@@ -465,36 +468,112 @@ fun HomeScreen() {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight(0.5f) .fillMaxHeight(0.5f)
.padding(24.dp), .padding(horizontal = 24.dp)
.navigationBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
) { ) {
Text( Text(
text = "Messaging Relays are required", text = "Messaging Relays are missing",
style = MaterialTheme.typography.headlineSmallEmphasized.copy( style = MaterialTheme.typography.titleLargeEmphasized.copy(
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
fontFamily = getExpressiveFontFamily()
), ),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
) )
Spacer(modifier = Modifier.size(8.dp)) Row(
Text( modifier = Modifier.fillMaxWidth(),
text = "Coop cannot found your messaging relays. To send and receive messages on Coop, you need to set up at least one messaging relay.", verticalAlignment = Alignment.CenterVertically,
style = MaterialTheme.typography.bodyLarge horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Surface(
modifier = Modifier.size(24.dp),
shape = MaterialShapes.Circle.toShape(),
color = MaterialTheme.colorScheme.error,
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Icon(
painter = painterResource(Res.drawable.ic_close),
contentDescription = "X",
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.onError
) )
Spacer(modifier = Modifier.size(8.dp)) }
}
Text(
text = "Other people won't be able to send you messages.",
style = MaterialTheme.typography.titleSmallEmphasized,
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Surface(
modifier = Modifier.size(24.dp),
shape = MaterialShapes.Circle.toShape(),
color = MaterialTheme.colorScheme.error,
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Icon(
painter = painterResource(Res.drawable.ic_close),
contentDescription = "X",
modifier = Modifier.size(16.dp),
tint = MaterialTheme.colorScheme.onError
)
}
}
Text(
text = "You cannot store your messages.",
style = MaterialTheme.typography.titleSmallEmphasized,
)
}
Text( Text(
text = "Please click the button below to continue with the default set of relays. You can always change them later in the settings.", text = "Please click the button below to continue with the default set of relays. You can always change them later in the settings.",
style = MaterialTheme.typography.bodyLarge.copy( style = MaterialTheme.typography.bodySmall.copy(
fontStyle = FontStyle.Italic,
),
)
Text(
text = "If you believe this is a mistake, please click the Retry button to check again.",
style = MaterialTheme.typography.bodySmall.copy(
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,
), ),
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
if (isBusy) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
LoadingIndicator()
}
} else {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
TextButton( TextButton(
onClick = { }, enabled = !isBusy,
onClick = {
scope.launch {
isBusy = true
try {
viewModel.refetchMsgRelays(currentUser)
} catch (e: Exception) {
snackbarHostState.showSnackbar("Failed to refresh metadata: ${e.message}")
}
isBusy = false
}
},
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.height(ButtonDefaults.MediumContainerHeight), .height(ButtonDefaults.MediumContainerHeight),
@@ -505,6 +584,7 @@ fun HomeScreen() {
) )
} }
Button( Button(
enabled = !isBusy,
onClick = { onClick = {
scope.launch { scope.launch {
viewModel.useDefaultMsgRelayList() viewModel.useDefaultMsgRelayList()
@@ -524,6 +604,7 @@ fun HomeScreen() {
} }
} }
} }
}
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)

View File

@@ -190,11 +190,6 @@ class Nostr {
} }
} }
suspend fun exit() {
signer.switch(Keys.generate())
deviceSigner = null
}
suspend fun setSigner(new: AsyncNostrSigner) { suspend fun setSigner(new: AsyncNostrSigner) {
try { try {
signer.switch(new) signer.switch(new)
@@ -650,6 +645,19 @@ class Nostr {
} }
} }
suspend fun fetchMsgRelays(publicKey: PublicKey): List<RelayUrl> {
try {
val kind = Kind.fromStd(KindStandard.INBOX_RELAYS)
val filter = Filter().kind(kind).author(publicKey).limit(1u)
val target = ReqTarget.auto(listOf(filter))
val events = client?.fetchEvents(target, timeout = Duration.parse("3s"))
return nip17ExtractRelayList(events?.toVec()?.firstOrNull() ?: return emptyList())
} catch (e: Exception) {
throw IllegalStateException("Failed to fetch msg relays: ${e.message}", e)
}
}
suspend fun getRelayList(publicKey: PublicKey): Map<RelayUrl, RelayMetadata?> { suspend fun getRelayList(publicKey: PublicKey): Map<RelayUrl, RelayMetadata?> {
try { try {
val kind = Kind.fromStd(KindStandard.RELAY_LIST) val kind = Kind.fromStd(KindStandard.RELAY_LIST)

View File

@@ -146,20 +146,6 @@ class NostrViewModel(
} }
} }
private fun processIncomingEvent(event: UnsignedEvent) {
val roomId = event.roomId()
val existingRoom = _chatRooms.value.firstOrNull { it.id == roomId }
if (existingRoom == null) {
nostr.signer.currentUser?.let { user ->
val newRoom = Room.new(event, user)
_chatRooms.update { (it + newRoom).sortedDescending().toSet() }
}
} else {
updateRoomList(roomId, event)
}
}
private suspend fun runObserver() = coroutineScope { private suspend fun runObserver() = coroutineScope {
// Observe new messages // Observe new messages
launch { launch {
@@ -298,13 +284,11 @@ class NostrViewModel(
nostr.getUserMetadata() nostr.getUserMetadata()
// Small delay to ensure all relays are connected // Small delay to ensure all relays are connected
delay(3000.milliseconds) delay(2.seconds)
// Check if the relay list is empty // Check if the relay list is empty
val relays = nostr.getMsgRelays(pubkey) val relays = nostr.getMsgRelays(pubkey)
if (relays.isEmpty()) { if (relays.isEmpty()) _isRelayListEmpty.value = true
_isRelayListEmpty.value = true
}
break break
} }
@@ -540,6 +524,11 @@ class NostrViewModel(
return externalSignerHandler?.isAvailable() == true return externalSignerHandler?.isAvailable() == true
} }
suspend fun refetchMsgRelays(pubkey: PublicKey) {
val relays = nostr.fetchMsgRelays(pubkey)
if (relays.isNotEmpty()) dismissRelayWarning()
}
suspend fun useDefaultMsgRelayList() { suspend fun useDefaultMsgRelayList() {
try { try {
val defaultRelays = nostr.getDefaultMsgRelayList() val defaultRelays = nostr.getDefaultMsgRelayList()