From 98453c7b65884847255044facd68b7ca00795f9e Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 25 Aug 2025 10:26:39 -0400 Subject: [PATCH 1/5] Fixes unable to delete relays from some lists. --- .../quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt | 7 ++----- .../vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt | 2 +- .../vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt | 2 +- .../quartz/nip51Lists/relayLists/tags/RelayTag.kt | 2 +- .../com/vitorpamplona/quartz/nip51Lists/tags/RelayTag.kt | 2 +- .../quartz/nip62RequestToVanish/tags/RelayTag.kt | 2 +- .../quartz/nip65RelayList/tags/AdvertisedRelayInfoTag.kt | 2 +- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt index 8c44924c2..94dc26f07 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt @@ -68,11 +68,8 @@ class ChatMessageRelayListEvent( val tags = earlierVersion.tags .filter(RelayTag::notMatch) - .plus( - relays.map { - RelayTag.assemble(it) - }, - ).toTypedArray() + .plus(relays.map { RelayTag.assemble(it) }) + .toTypedArray() return signer.sign(createdAt, KIND, tags, earlierVersion.content) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt index d1c50cf20..b609c3509 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt @@ -34,7 +34,7 @@ class RelayTag { fun match(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic - fun notMatch(tag: Array) = tag.has(0) && tag[0] == TAG_NAME + fun notMatch(tag: Array) = !(tag.has(0) && tag[0] == TAG_NAME) @JvmStatic fun parse(tag: Array): NormalizedRelayUrl? { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt index bce30729f..498d2b19a 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt @@ -33,7 +33,7 @@ class RelayTag { fun match(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic - fun notMatch(tag: Array) = tag.has(0) && tag[0] == TAG_NAME + fun notMatch(tag: Array) = !(tag.has(0) && tag[0] == TAG_NAME) @JvmStatic fun parse(tag: Array): NormalizedRelayUrl? { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/relayLists/tags/RelayTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/relayLists/tags/RelayTag.kt index b7c7261cb..bc24afe51 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/relayLists/tags/RelayTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/relayLists/tags/RelayTag.kt @@ -33,7 +33,7 @@ class RelayTag { fun match(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic - fun notMatch(tag: Array) = tag.has(0) && tag[0] == TAG_NAME + fun notMatch(tag: Array) = !(tag.has(0) && tag[0] == TAG_NAME) @JvmStatic fun parse(tag: Array): NormalizedRelayUrl? { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/tags/RelayTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/tags/RelayTag.kt index 50b700f64..d56776690 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/tags/RelayTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/tags/RelayTag.kt @@ -33,7 +33,7 @@ class RelayTag { fun match(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic - fun notMatch(tag: Array) = tag.has(0) && tag[0] == TAG_NAME + fun notMatch(tag: Array) = !(tag.has(0) && tag[0] == TAG_NAME) @JvmStatic fun parse(tag: Array): NormalizedRelayUrl? { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip62RequestToVanish/tags/RelayTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip62RequestToVanish/tags/RelayTag.kt index 13a1c1eb3..7af462f44 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip62RequestToVanish/tags/RelayTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip62RequestToVanish/tags/RelayTag.kt @@ -33,7 +33,7 @@ class RelayTag { fun match(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() @JvmStatic - fun notMatch(tag: Array) = tag.has(0) && tag[0] == TAG_NAME + fun notMatch(tag: Array) = !(tag.has(0) && tag[0] == TAG_NAME) @JvmStatic fun shouldVanishFrom( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip65RelayList/tags/AdvertisedRelayInfoTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip65RelayList/tags/AdvertisedRelayInfoTag.kt index 20eb69ac4..a588ec4c7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip65RelayList/tags/AdvertisedRelayInfoTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip65RelayList/tags/AdvertisedRelayInfoTag.kt @@ -38,7 +38,7 @@ class AdvertisedRelayInfo( fun match(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() - fun notMatch(tag: Array) = !match(tag) + fun notMatch(tag: Array) = !(tag.has(0) && tag[0] == TAG_NAME) @JvmStatic fun parse(tag: Array): AdvertisedRelayInfo? { From 0187529f8d7f1ea8c2ce2f09a912e45ee1ea0fc5 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 25 Aug 2025 12:16:40 -0400 Subject: [PATCH 2/5] Removes default and bootstrapping lists from the "Trusted Relay set". This should fix the unwanted NOTIFY requests. --- .../searchRelays/SearchRelayListState.kt | 20 ++++++++++--- .../nip65RelayList/Nip65RelayListState.kt | 30 +++++++++++-------- .../serverList/TrustedRelayListsState.kt | 4 +-- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/searchRelays/SearchRelayListState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/searchRelays/SearchRelayListState.kt index 3c5f2a25f..2a62ca3d1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/searchRelays/SearchRelayListState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip51Lists/searchRelays/SearchRelayListState.kt @@ -57,10 +57,11 @@ class SearchRelayListState( fun getSearchRelayList(): SearchRelayListEvent? = getSearchRelayListNote().event as? SearchRelayListEvent - suspend fun normalizeSearchRelayListWithBackup(note: Note): Set { - val event = note.event as? SearchRelayListEvent ?: settings.backupSearchRelayList - return event?.let { decryptionCache.relays(it) } ?: DefaultSearchRelayList - } + fun searchListEvent(note: Note) = note.event as? SearchRelayListEvent ?: settings.backupSearchRelayList + + suspend fun normalizeSearchRelayListWithBackup(note: Note): Set = searchListEvent(note)?.let { decryptionCache.relays(it) }?.ifEmpty { null } ?: DefaultSearchRelayList + + suspend fun normalizeSearchRelayListWithBackupNoDefaults(note: Note): Set = searchListEvent(note)?.let { decryptionCache.relays(it) } ?: emptySet() val flow = getSearchRelayListFlow() @@ -73,6 +74,17 @@ class SearchRelayListState( emptySet(), ) + val flowNoDefaults = + getSearchRelayListFlow() + .map { normalizeSearchRelayListWithBackupNoDefaults(it.note) } + .onStart { emit(normalizeSearchRelayListWithBackupNoDefaults(getSearchRelayListNote())) } + .flowOn(Dispatchers.Default) + .stateIn( + scope, + SharingStarted.Companion.Eagerly, + emptySet(), + ) + suspend fun saveRelayList(searchRelays: List): SearchRelayListEvent { val relayListForSearch = getSearchRelayList() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip65RelayList/Nip65RelayListState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip65RelayList/Nip65RelayListState.kt index 9df03f441..acd8c8479 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip65RelayList/Nip65RelayListState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip65RelayList/Nip65RelayListState.kt @@ -57,20 +57,15 @@ class Nip65RelayListState( fun getNIP65RelayList(): AdvertisedRelayListEvent? = getNIP65RelayListNote().event as? AdvertisedRelayListEvent - fun normalizeNIP65WriteRelayListWithBackup(note: Note): Set { - val event = note.event as? AdvertisedRelayListEvent ?: settings.backupNIP65RelayList - return event?.writeRelaysNorm()?.toSet() ?: Constants.eventFinderRelays - } + fun nip65Event(note: Note) = note.event as? AdvertisedRelayListEvent ?: settings.backupNIP65RelayList - fun normalizeNIP65ReadRelayListWithBackup(note: Note): Set { - val event = note.event as? AdvertisedRelayListEvent ?: settings.backupNIP65RelayList - return event?.readRelaysNorm()?.toSet() ?: Constants.eventFinderRelays - } + fun normalizeNIP65WriteRelayListWithBackup(note: Note): Set = nip65Event(note)?.writeRelaysNorm()?.toSet() ?: Constants.eventFinderRelays - fun normalizeNIP65AllRelayListWithBackup(note: Note): Set { - val event = note.event as? AdvertisedRelayListEvent ?: settings.backupNIP65RelayList - return event?.relays()?.map { it.relayUrl }?.toSet() ?: Constants.eventFinderRelays - } + fun normalizeNIP65ReadRelayListWithBackup(note: Note): Set = nip65Event(note)?.readRelaysNorm()?.toSet() ?: Constants.eventFinderRelays + + fun normalizeNIP65AllRelayListWithBackup(note: Note): Set = nip65Event(note)?.relays()?.map { it.relayUrl }?.toSet() ?: Constants.eventFinderRelays + + fun normalizeNIP65AllRelayListWithBackupNoDefaults(note: Note): Set = nip65Event(note)?.relays()?.map { it.relayUrl }?.toSet() ?: emptySet() val outboxFlow = getNIP65RelayListFlow() @@ -105,6 +100,17 @@ class Nip65RelayListState( emptySet(), ) + val allFlowNoDefaults = + getNIP65RelayListFlow() + .map { normalizeNIP65AllRelayListWithBackupNoDefaults(it.note) } + .onStart { emit(normalizeNIP65AllRelayListWithBackupNoDefaults(getNIP65RelayListNote())) } + .flowOn(Dispatchers.Default) + .stateIn( + scope, + SharingStarted.Eagerly, + emptySet(), + ) + suspend fun saveRelayList(relays: List): AdvertisedRelayListEvent { val nip65RelayList = getNIP65RelayList() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/TrustedRelayListsState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/TrustedRelayListsState.kt index 743481c34..7dd7ab05b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/TrustedRelayListsState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/serverList/TrustedRelayListsState.kt @@ -52,11 +52,11 @@ class TrustedRelayListsState( val flow: StateFlow> = combine( listOf( - nip65RelayList.allFlow, + nip65RelayList.allFlowNoDefaults, privateOutboxRelayList.flow, localRelayList.flow, dmRelayList.flow, - searchRelayListState.flow, + searchRelayListState.flowNoDefaults, trustedRelayList.flow, broadcastRelayList.flow, ), From 823540160a5b8c1ae8d99abc78d2c8f57f24ea9c Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 25 Aug 2025 15:42:57 -0400 Subject: [PATCH 3/5] Fixes some DM notification issues --- .../amethyst/service/notifications/EventNotificationConsumer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index 625c6cf2d..4b7723b71 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -167,7 +167,7 @@ class EventNotificationConsumer( // this is not verifiable if (LocalCache.justConsume(inner, null, true)) { - event + inner } else { null } From 7302d260d97c9ae07997f8bb2d95de5a51d4db24 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 25 Aug 2025 15:43:12 -0400 Subject: [PATCH 4/5] avoids sending expired events to the cloud --- .../main/java/com/vitorpamplona/amethyst/model/LocalCache.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 06a73c047..c6a509e5f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -2619,7 +2619,7 @@ object LocalCache : ILocalCache { // updates relay with a new event. getAddressableNoteIfExists(event.address())?.let { note -> note.event?.let { existingEvent -> - if (existingEvent.createdAt > event.createdAt && !note.hasRelay(relay.url) && !deletionIndex.hasBeenDeleted(event)) { + if (existingEvent.createdAt > event.createdAt && !note.hasRelay(relay.url) && !deletionIndex.hasBeenDeleted(event) && !event.isExpired()) { if (isDebug) { Log.d("LocalCache", "Updating ${relay.url.url} with a new version of ${event.kind} ${event.id} to ${existingEvent.id}") } From fd752a499740425f126f3d2185768c622cf1f3e2 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 25 Aug 2025 15:43:56 -0400 Subject: [PATCH 5/5] Adds a restart of the registration once the Post_notification permission changes --- .../ui/screen/loggedIn/LoggedInPage.kt | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt index 3aeabc0fc..7ba6db579 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt @@ -20,9 +20,11 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn +import android.Manifest import android.app.Activity import android.content.ActivityNotFoundException import android.content.Intent +import android.os.Build import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -30,11 +32,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.isGranted +import com.google.accompanist.permissions.rememberPermissionState import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.R @@ -43,6 +47,7 @@ import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.RelayAuthSubscription import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.navigation.AppNavigation +import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.scope import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel @@ -51,7 +56,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.Discove import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.VideoFilterAssemblerSubscription import com.vitorpamplona.quartz.nip55AndroidSigner.client.IActivityLauncher -import kotlinx.coroutines.Job import kotlinx.coroutines.launch @Composable @@ -140,15 +144,31 @@ fun ManageRelayServices(accountViewModel: AccountViewModel) { .collectAsStateWithLifecycle() } +@OptIn(ExperimentalPermissionsApi::class) @Composable fun NotificationRegistration(accountViewModel: AccountViewModel) { - val scope = rememberCoroutineScope() - var job = remember { null } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val scope = rememberCoroutineScope() - LifecycleResumeEffect(key1 = accountViewModel) { - Log.d("RegisterAccounts", "Registering for push notifications") - job?.cancel() - job = + val notificationPermissionState = rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS) + + if (notificationPermissionState.status.isGranted) { + LifecycleResumeEffect(key1 = accountViewModel, notificationPermissionState.status.isGranted) { + Log.d("RegisterAccounts", "Registering for push notifications $notificationPermissionState") + scope.launch { + PushNotificationUtils.checkAndInit( + LocalPreferences.allSavedAccounts(), + accountViewModel::okHttpClientForTrustedRelays, + ) + } + + onPauseOrDispose {} + } + } + } else { + // no need for push permissions before 33 + LifecycleResumeEffect(key1 = accountViewModel) { + Log.d("RegisterAccounts", "Registering for push notifications") scope.launch { PushNotificationUtils.checkAndInit( LocalPreferences.allSavedAccounts(), @@ -156,8 +176,7 @@ fun NotificationRegistration(accountViewModel: AccountViewModel) { ) } - onPauseOrDispose { - job.cancel() + onPauseOrDispose {} } } }