diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 7a3c1ac78..39d36c3d0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -42,6 +42,8 @@ import com.vitorpamplona.quartz.encoders.Nip47WalletConnect import com.vitorpamplona.quartz.encoders.hexToByteArray import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toNpub +import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent +import com.vitorpamplona.quartz.events.ChatMessageRelayListEvent import com.vitorpamplona.quartz.events.ContactListEvent import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.LnZapEvent @@ -90,6 +92,8 @@ private object PrefKeys { const val DEFAULT_DISCOVERY_FOLLOW_LIST = "defaultDiscoveryFollowList" const val ZAP_PAYMENT_REQUEST_SERVER = "zapPaymentServer" const val LATEST_CONTACT_LIST = "latestContactList" + const val LATEST_DM_RELAY_LIST = "latestDMRelayList" + const val LATEST_NIP65_RELAY_LIST = "latestNIP65RelayList" const val HIDE_DELETE_REQUEST_DIALOG = "hide_delete_request_dialog" const val HIDE_BLOCK_ALERT_DIALOG = "hide_block_alert_dialog" const val HIDE_NIP_17_WARNING_DIALOG = "hide_nip24_warning_dialog" // delete later @@ -311,10 +315,33 @@ object LocalPreferences { PrefKeys.ZAP_PAYMENT_REQUEST_SERVER, Event.mapper.writeValueAsString(account.zapPaymentRequest), ) - putString( - PrefKeys.LATEST_CONTACT_LIST, - Event.mapper.writeValueAsString(account.backupContactList), - ) + if (account.backupContactList != null) { + putString( + PrefKeys.LATEST_CONTACT_LIST, + Event.mapper.writeValueAsString(account.backupContactList), + ) + } else { + remove(PrefKeys.LATEST_CONTACT_LIST) + } + + if (account.backupDMRelayList != null) { + putString( + PrefKeys.LATEST_DM_RELAY_LIST, + Event.mapper.writeValueAsString(account.backupDMRelayList), + ) + } else { + remove(PrefKeys.LATEST_DM_RELAY_LIST) + } + + if (account.backupNIP65RelayList != null) { + putString( + PrefKeys.LATEST_NIP65_RELAY_LIST, + Event.mapper.writeValueAsString(account.backupNIP65RelayList), + ) + } else { + remove(PrefKeys.LATEST_NIP65_RELAY_LIST) + } + putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog) putBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, account.hideNIP17WarningDialog) putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog) @@ -471,8 +498,8 @@ object LocalPreferences { val latestContactList = try { getString(PrefKeys.LATEST_CONTACT_LIST, null)?.let { - println("Decoding Contact List: " + it) - if (it != null) { + if (it != "null") { + println("Decoding Contact List: $it") Event.fromJson(it) as ContactListEvent? } else { null @@ -488,6 +515,46 @@ object LocalPreferences { null } + val latestDmRelayList = + try { + getString(PrefKeys.LATEST_DM_RELAY_LIST, null)?.let { + if (it != "null") { + println("Decoding DM Relay List: $it") + Event.fromJson(it) as ChatMessageRelayListEvent? + } else { + null + } + } + } catch (e: Throwable) { + if (e is CancellationException) throw e + Log.w( + "LocalPreferences", + "Error Decoding DM Relay List ${getString(PrefKeys.LATEST_DM_RELAY_LIST, null)}", + e, + ) + null + } + + val latestNip65RelayList = + try { + getString(PrefKeys.LATEST_NIP65_RELAY_LIST, null)?.let { + if (it != "null") { + println("Decoding NIP65 Relay List: $it") + Event.fromJson(it) as AdvertisedRelayListEvent? + } else { + null + } + } + } catch (e: Throwable) { + if (e is CancellationException) throw e + Log.w( + "LocalPreferences", + "Error Decoding NIP65 Relay List ${getString(PrefKeys.LATEST_NIP65_RELAY_LIST, null)}", + e, + ) + null + } + val pendingAttestations = try { getString(PrefKeys.PENDING_ATTESTATIONS, null)?.let { @@ -502,7 +569,7 @@ object LocalPreferences { if (e is CancellationException) throw e Log.w( "LocalPreferences", - "Error Decoding Contact List ${getString(PrefKeys.LATEST_CONTACT_LIST, null)}", + "Error Decoding Contact List ${getString(PrefKeys.PENDING_ATTESTATIONS, null)}", e, ) null @@ -595,6 +662,8 @@ object LocalPreferences { hideBlockAlertDialog = hideBlockAlertDialog, hideNIP17WarningDialog = hideNIP17WarningDialog, backupContactList = latestContactList, + backupNIP65RelayList = latestNip65RelayList, + backupDMRelayList = latestDmRelayList, proxy = proxy, proxyPort = proxyPort, showSensitiveContent = MutableStateFlow(showSensitiveContent), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 1e3ff69ff..2fd531131 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -116,6 +116,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flattenMerge @@ -192,6 +193,8 @@ class Account( var hideBlockAlertDialog: Boolean = false, var hideNIP17WarningDialog: Boolean = false, var backupContactList: ContactListEvent? = null, + var backupDMRelayList: ChatMessageRelayListEvent? = null, + var backupNIP65RelayList: AdvertisedRelayListEvent? = null, var proxy: Proxy? = null, var proxyPort: Int = 9050, var showSensitiveContent: MutableStateFlow = MutableStateFlow(null), @@ -269,7 +272,9 @@ class Account( if (mappedRelaySet.none { it.url == newUrl }) { mappedRelaySet = mappedRelaySet + RelaySetupInfo( - newUrl, true, true, + newUrl, + true, + true, setOf( FeedType.PRIVATE_DMS, ), @@ -294,7 +299,9 @@ class Account( if (mappedRelaySet.none { it.url == newUrl }) { mappedRelaySet = mappedRelaySet + RelaySetupInfo( - newUrl, true, false, + newUrl, + true, + false, setOf( FeedType.SEARCH, ), @@ -319,9 +326,14 @@ class Account( if (mappedRelaySet.none { it.url == newUrl }) { mappedRelaySet = mappedRelaySet + RelaySetupInfo( - newUrl, true, true, + newUrl, + true, + true, setOf( - FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.GLOBAL, FeedType.PRIVATE_DMS, + FeedType.FOLLOWS, + FeedType.PUBLIC_CHATS, + FeedType.GLOBAL, + FeedType.PRIVATE_DMS, ), ) } @@ -344,9 +356,14 @@ class Account( if (mappedRelaySet.none { it.url == newUrl }) { mappedRelaySet = mappedRelaySet + RelaySetupInfo( - newUrl, true, true, + newUrl, + true, + true, setOf( - FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.GLOBAL, FeedType.PRIVATE_DMS, + FeedType.FOLLOWS, + FeedType.PUBLIC_CHATS, + FeedType.GLOBAL, + FeedType.PRIVATE_DMS, ), ) } @@ -363,10 +380,14 @@ class Account( val write = nip65setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.BOTH || nip65setup.type == AdvertisedRelayListEvent.AdvertisedRelayType.READ RelaySetupInfo( - relay.url, true, relay.write || write, + relay.url, + true, + relay.write || write, relay.feedTypes + setOf( - FeedType.FOLLOWS, FeedType.GLOBAL, FeedType.PUBLIC_CHATS, + FeedType.FOLLOWS, + FeedType.GLOBAL, + FeedType.PUBLIC_CHATS, ), ) } else { @@ -380,9 +401,12 @@ class Account( mappedRelaySet = mappedRelaySet + RelaySetupInfo( - newNip65Setup.relayUrl, true, write, + newNip65Setup.relayUrl, + true, + write, setOf( - FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, + FeedType.FOLLOWS, + FeedType.PUBLIC_CHATS, ), ) } @@ -2635,6 +2659,26 @@ class Account( } } + private fun updateDMRelayList(newDMRelayList: ChatMessageRelayListEvent?) { + if (newDMRelayList == null || newDMRelayList.tags.isEmpty()) return + + // Events might be different objects, we have to compare their ids. + if (backupDMRelayList?.id != newDMRelayList.id) { + backupDMRelayList = newDMRelayList + saveable.invalidateData() + } + } + + private fun updateNIP65RelayList(newNIP65RelayList: AdvertisedRelayListEvent?) { + if (newNIP65RelayList == null || newNIP65RelayList.tags.isEmpty()) return + + // Events might be different objects, we have to compare their ids. + if (backupNIP65RelayList?.id != newNIP65RelayList.id) { + backupNIP65RelayList = newNIP65RelayList + saveable.invalidateData() + } + } + // Takes a User's relay list and adds the types of feeds they are active for. fun activeRelays(): Array? { val usersRelayList = @@ -2968,8 +3012,32 @@ class Account( suspend fun registerObservers() = withContext(Dispatchers.Main) { // saves contact list for the next time. - userProfile().live().follows.observeForever { - GlobalScope.launch(Dispatchers.IO) { updateContactListTo(userProfile().latestContactList) } + scope.launch { + Log.d("AccountRegisterObservers", "Kind 3 Collector Start") + userProfile().flow().follows.stateFlow.collect { + Log.d("AccountRegisterObservers", "Updating Kind 3 ${userProfile().toBestDisplayName()}") + updateContactListTo(userProfile().latestContactList) + } + } + + scope.launch { + Log.d("AccountRegisterObservers", "NIP-17 Relay List Collector Start") + getDMRelayListFlow().collect { + Log.d("AccountRegisterObservers", "Updating DM Relay List for ${userProfile().toBestDisplayName()}") + (it.note.event as? ChatMessageRelayListEvent)?.let { + updateDMRelayList(it) + } + } + } + + scope.launch { + Log.d("AccountRegisterObservers", "NIP-65 Relay List Collector Start") + getNIP65RelayListFlow().collect { + Log.d("AccountRegisterObservers", "Updating NIP-65 List for ${userProfile().toBestDisplayName()}") + (it.note.event as? AdvertisedRelayListEvent)?.let { + updateNIP65RelayList(it) + } + } } // imports transient blocks due to spam. @@ -2990,7 +3058,7 @@ class Account( } init { - Log.d("Init", "Account") + Log.d("Account", "Init") backupContactList?.let { println("Loading saved contacts ${it.toJson()}") @@ -2998,6 +3066,16 @@ class Account( GlobalScope.launch(Dispatchers.IO) { LocalCache.consume(it) } } } + + backupDMRelayList?.let { + println("Loading DM Relay List ${it.toJson()}") + GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) } + } + + backupNIP65RelayList?.let { + println("Loading saved contacts ${it.toJson()}") + GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) } + } } }