diff --git a/.claude/skills/quartz-integration/SKILL.md b/.claude/skills/quartz-integration/SKILL.md
index 516903f50..a103377d8 100644
--- a/.claude/skills/quartz-integration/SKILL.md
+++ b/.claude/skills/quartz-integration/SKILL.md
@@ -54,7 +54,6 @@ Quartz exposes these as `api` (you get them transitively):
| `fr.acinq.secp256k1:secp256k1-kmp-*` | Schnorr signing |
| `com.github.anthonynsimon:rfc3986-normalizer` | Relay URL normalization |
| `com.fasterxml.jackson.module:jackson-module-kotlin` | Event JSON parsing |
-| `com.linkedin.urls:url-detector` | URL extraction from content |
For Android, add to `build.gradle.kts`:
```kotlin
diff --git a/.claude/skills/quartz-integration/references/gradle-setup.md b/.claude/skills/quartz-integration/references/gradle-setup.md
index 7a17c0be5..e1d989521 100644
--- a/.claude/skills/quartz-integration/references/gradle-setup.md
+++ b/.claude/skills/quartz-integration/references/gradle-setup.md
@@ -92,7 +92,6 @@ dependencies {
### JVM + Android (`jvmAndroid`)
- `com.github.anthonynsimon:rfc3986-normalizer` (URL normalization)
- `com.fasterxml.jackson.module:jackson-module-kotlin` (JSON)
-- `com.linkedin.urls:url-detector` (URL extraction)
- `com.squareup.okhttp3:okhttp` (WebSocket)
- `ru.gildor.coroutines:kotlin-coroutines-okhttp`
- `nl.bommber:kchesslib` (NIP-64 chess, version pinned to 1.0.0)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8fa1598e..7a6fc1505 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,185 @@
+Adds support for creating and rendering NIP-85 Polls
+
+Adds support for Relay Feeds
+- Adds support for NIP-51 favorite relay feeds
+- Shows favorite relays in the top navigation filter
+- Clicking wss:// links shows the global feed for that relay.
+- New user account adds nostr.wine to favorite relay feeds
+
+Redesigns Media Player
+- Redesigned player controls for videos, audios, and picture-in-picture.
+- Adds our own buttons and indicators for the video playback
+- Adds Music support with waveform animations
+- Migrates to new Media3 content view frames
+- Improved Picture in Picture actions
+- Turn video controller creation into a flow to fix playback lifecycle issues
+- Adds support for uploading audio
+
+Adds support for NIP events (kind 30817)
+
+Adds support for NIP-52 Calendar appointments
+
+Adds support for NIP-39 External Identities with kind 10011
+
+Adds support for NIP-66 Relay Monitor and discovery support to Quartz
+
+Adds support for NIP-C0 Code Snippets to Quartz
+
+Adds support for NIP-A3 Payment targets (PayTo: 10133) by @npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5
+
+Adds support for Namecoin .bit urls to NIP-05
+- Adds choice of ElectrumX server to resolve namecoins.
+
+Adds basic support for Chess with Jester protocol
+
+Adds NIP-46 support to Quartz and Amethyst Desktop
+
+Adds a Broadcasting feedback pop-up in the Complete UI mode
+
+Adds support for rendering Zap events when quoted inside of posts.
+
+Removes support for NIP-96 and updates Blossom recommendations
+
+Adds support to upload Documents to all new post screens.
+
+Redesigns and reorganizes Setting pages
+- Consolidate drawer settings into a single Settings hub screen
+- Redesigns Zap Amount and NWC setup screens
+- Redesigns Custom zap amount screens
+- Add reactions row settings (enable/disable, order, show/hide counters) by @npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5
+- Tapping on Zap without any pre-configured amount opens the custom dialog
+
+URL/URI parser rewrite in Kotlin multiplatform (KMP)
+- Fixes characters attached to URLs or nostr URLs without a space
+- Massively increases parsing performance
+- Treat multibyte characters as URL terminators in RichTextParser by @npub1k0jrarx8um0lyw3nmysn50539ky4k8p7gfgzgrsvn8d7lccx3d0s38dczd
+- Adds a parser for blossom: uris
+
+Minimizes parent thread rendering in quoted notes by @npub1aeh2zw4elewy5682lxc6xnlqzjnxksq303gwu2npfaxd49vmde6qcq4nwx
+
+Fixes bug on Show More calculations for very long texts without spaces
+
+Relay Management:
+- Adds relay search tooltip when adding relays
+- Adds the list of keys using each relay to the relay information
+- Adds active subscriptions and outbox event in the queue to relay information
+- Adds a complete list of event kind names to the subscription card to relay information
+- Tracks and displays connection success rate on relay settings
+
+Search fixes
+- Breaks the search filter into two subscriptions to prioritize Metadata without punishing content.
+- Fixes the need to start user searches with @ in user fields
+- Fixes the stability of the search feed when the user navigates away and back.
+- Replaces about me for NIP-05 in the user search results
+- Adds relay URL search to the search page
+- Forces returning one user when searching by nip-05
+- Removes outdated versions of addressables from the search results
+
+Profiles:
+- Adds a profile picture upload button when the user has no picture
+- Groups received zap amounts by sending the user in the profile tab
+- Increases the limit of Zap downloads for profiles to 1000
+- Simplifies profile edit screen layout by @npub1aeh2zw4elewy5682lxc6xnlqzjnxksq303gwu2npfaxd49vmde6qcq4nwx
+- Migrates profile galleries to display a thumbnail for videos
+- Fixes profile galleries' aspect ratios
+
+Bulk Follow onboarding
+- Adds screens to search for a user and to copy his/her follow list
+
+Voice message support by @npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef
+- Adds voice anonymization
+- Change from "hold to record" to "click to start, click to stop"
+- Display kind 1 voice replies as an audio waveform
+- Increases max voice record duration to 600 seconds
+- Switches the public message event to use quoted posts for replies
+
+Fixes:
+- Fixes "forked from" label rendering over the name
+- Avoids crashing when the `k` tag cannot be parsed to a number
+- Only use Voice Reply events when replying to voice notes. Others just receive a URL.
+- Fixes the lack of update in the follow count on the UserProfile page
+- Fixes out of memory when downloading large videos
+- Fixes Jackson deserialization for empty Filters and add regression test by @npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5
+- Fixes NullPointerException when the filter contains tags
+- Fixes download cancellations when screen components disappear
+- Migrates to use "title" instead of "name" tags for NIP-51 lists
+- Adds a longer crop for npubs so that we can see vanity keys better
+- Fixes the need to have tags and kinds for inbox.nostr.wine to work
+- Blocks the size of Relay Auth Status arrays from growing forever with auth messages
+- Fixes crash when getting OpenGraph tags of invalid URLs
+- Fixes NIP-44 key mutation in NIP-46 connect
+- Location permission watcher moved outside screens to avoid recreation
+
+AI:
+- Add SKILL.md for AI agent customization
+
+Defaults:
+- Switches wss://nostr.band to wss://antiprimal.net, wss://relay.ditto.pub on app defaults
+- Adds wss://nostr.wine, wss://news.utxo.one as favorite relay feeds
+- Adds wss://directory.yabu.me and wss://profiles.nostr1.com as index relays
+- Adds electrumx.testls.space, nmc2.bitcoins.sk, 46.229.238.187 and i665jpwsq46zlsdbnj4axgzd3s56uzey5uhotsnxzsknzbn36jaddsid.onion as ElectrumX servers
+
+Adds complete support for iOS to Quartz by @npub1a3tx8wcrt789skl6gg7rqwj4wey0j53eesr4z6asd4h4jwrd62jq0wkq4k
+- Provide implementation for Rfc3986 on iOS, using the Swift Rfc3986UriBridge.
+- Provide implementation for LargeCache, using a CacheMap.
+- Provide implementation for fastFindURLs()
+- Provide implementation for makeAbsoluteIfRelativeUrl() in ServerInfoParser.ios.kt
+- Provide implementation for UrlEncoder.
+- Provide implementation for UnicodeNormalizer.
+- Provide implementation for GZip compression/decompression. Some small fixes in URLs.ios.kt
+- Provide implementation for AESCBC.
+- Provide implementation for AESGCM.
+- Provide implementation for DigestInstance.
+- Adds a reqUntilEoseAsFlow extension to the Nostr Client
+
+Amethyst Desktop by @npub12cfje6nl2nuxplcqfvhg7ljt89fmpj0n0fd24zxsukja5qm9wmtqd7y76c
+- Adds NIP-46 Bunker Login
+- Adds Support for Chess
+- Adds Thread Screens
+- Adds encrypted DMs (NIP-04/NIP-17)
+- Adds proper empty states with EOSE tracking
+- Adds multi-column deck layout
+- Clear stored credentials on logout
+- Adds bunker heartbeat indicator
+- Adds QR-based signer pairing
+- Migrates lifecycle-viewmodel KMP dependencies to KMP/Commons
+- Migrates drawReplyLevel modifier to KMP/Commons
+- Migrates ThreadFilter to KMP/Commons
+- Migrates Card interface and CardFeedState to KMP/Commons
+- Migrates Channels (public chats, ephemeral channels, and live streams) Account modules to KMP/Commons
+- Migrates private chatroom models to KMP/Commons
+- Migrates reports states to KMP/Commons
+- Migrates Emoji State to KMP/Commons
+- Migrates lud06 to lud16 mapping to KMP/Quartz
+- Migrates the new LocalCache observables to KMP/Commons
+- Migrates rich text parser from JVM to KMP/Commons
+
+Code Quality
+- Migrates to AGP 9.0
+- Removes the in-app memory counter methods
+- Refactors the old NIP-05 code on Quartz
+- Migrates contact list management to addressable notes
+- Creates new observable flows for LocalCache.
+- Moves metadata methods from User to UserCache objects
+- Separate Addressable vs Replaceable event class bases
+- Avoid dependency on AccountSettings for NwcSignerState
+- Finishes the transition to EventHint objects for building events.
+- Lots of code review fixes by @npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef
+- Large accessibility review by @npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef
+- Moves Top Nav Filter markers from Strings to full objects.
+- Removes support for feed definitions
+- AccountState refactoring
+
+Updated translations:
+- Czech, German, Swedish, and Portuguese by @npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef
+- Hungarian by @npub1dnvslq0vvrs8d603suykc4harv94yglcxwna9sl2xu8grt2afm3qgfh0tp
+- French by @npub106efcyntxc5qwl3w8krrhyt626m59ya2nk9f40px5s968u5xdwhsjsr8fz
+- Polish by @npub16gjyljum0ksrrm28zzvejydgxwfm7xse98zwc4hlgq8epxeuggushqwyrm
+- Hindi by @npub1ww6huwu3xye6r05n3qkjeq62wds5pq0jswhl7uc59lchc0n0ns4sdtw5e6
+- Slovenian by @npub1qqqqqqz7nhdqz3uuwmzlflxt46lyu7zkuqhcapddhgz66c4ddynswreecw
+- Bengali by @npub13qtw3yu0uc9r4yj5x0rhgy8nj5q0uyeq0pavkgt9ly69uuzxgkfqwvx23t
+- Chinese by hypnotichemionus4
+
# [Release v1.05.1: BugFixes](https://github.com/vitorpamplona/amethyst/releases/tag/v1.05.0) - 2025-01-08
diff --git a/README.md b/README.md
index 28219bf87..d1d9dbe7b 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases)
- [x] Request to Vanish (NIP-62)
- [x] Chess (NIP-64)
- [x] Relay List Metadata (NIP-65)
-- [ ] Relay Discovery and Monitoring (NIP-66)
+- [x] Relay Discovery and Monitoring (NIP-66)
- [x] Picture-first feeds (NIP-68)
- [ ] Peer-to-peer Orders (NIP-69)
- [o] Protected Events (NIP-70)
@@ -136,7 +136,7 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases)
- [ ] Web Bookmarks
- [x] Blossom (NIP-B7)
- [ ] Nostr BLE Communications Protocol (NIP-BE)
-- [ ] Code Snippets (NIP-C0)
+- [x] Code Snippets (NIP-C0)
- [ ] NIP-29 Chats (NIP-C7)
- [ ] MLS Protocol (NIP-EE)
- [x] Audio Tracks (zapstr.live) (kind:31337)
@@ -151,6 +151,7 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases)
- [x] Medical Data (NIP-xx/Draft)
- [x] Embed events (NIP-xx/Draft)
- [x] Edit Short Notes (NIP-xx/Draft)
+- [x] NIP Events (NIP-xx/Draft)
- [ ] Relationship Status (NIP-xx/Draft)
- [ ] Signed Filters (NIP-xx/Draft)
- [ ] Key Migration (NIP-xx/Draft)
@@ -158,7 +159,6 @@ height="70">](https://github.com/vitorpamplona/amethyst/releases)
- [x] Video Capture in the app
- [ ] Local Database
- [ ] Workspaces
-- [ ] Infinity Scroll
## Privacy and Information Permanence
diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
index 3aebe1c1c..a5f447e65 100644
--- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
+++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
@@ -80,7 +80,7 @@ class ImageUploadTesting {
.addInterceptor(DefaultContentTypeInterceptor("Amethyst/${BuildConfig.VERSION_NAME}"))
.build()
- private suspend fun getBitmap(): ByteArray {
+ private fun getBitmap(): ByteArray {
val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
for (x in 0 until bitmap.width) {
for (y in 0 until bitmap.height) {
@@ -127,7 +127,7 @@ class ImageUploadTesting {
// assertEquals(server.baseUrl, "${server.baseUrl}/$initialHash", result.url?.removeSuffix(".png"))
val imageData: ByteArray =
- ImageDownloader().waitAndGetImage(result.url!!, { client })?.bytes
+ ImageDownloader().waitAndGetImage(result.url!!) { client }?.bytes
?: run {
fail("${server.name}: Should not be null")
return
@@ -142,8 +142,7 @@ class ImageUploadTesting {
ServerInfoRetriever()
.loadInfo(
server.baseUrl,
- { client },
- )
+ ) { client }
val payload = getBitmap()
val inputStream = payload.inputStream()
@@ -172,7 +171,7 @@ class ImageUploadTesting {
Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http"))
val imageData: ByteArray =
- ImageDownloader().waitAndGetImage(url, { client })?.bytes
+ ImageDownloader().waitAndGetImage(url) { client }?.bytes
?: run {
fail("${server.name}: Should not be null")
return
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt
index c05fa392c..5885c1e22 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt
@@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
import com.vitorpamplona.amethyst.model.nip03Timestamp.IncomingOtsEventVerifier
import com.vitorpamplona.amethyst.model.nip03Timestamp.TorAwareOkHttpOtsResolverBuilder
import com.vitorpamplona.amethyst.model.nip11RelayInfo.Nip11CachedRetriever
+import com.vitorpamplona.amethyst.model.preferences.NamecoinSharedPreferences
import com.vitorpamplona.amethyst.model.preferences.TorSharedPreferences
import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences
import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilder
@@ -105,6 +106,11 @@ class AppModules(
TorSharedPreferences(appContext, applicationIOScope)
}
+ // Namecoin ElectrumX server preferences (global, like Tor settings)
+ val namecoinPrefs by lazy {
+ NamecoinSharedPreferences(appContext, applicationIOScope)
+ }
+
// App services that should be run as soon as there are subscribers to their flows
val locationManager = LocationState(appContext, applicationIOScope)
val connManager = ConnectivityManager(appContext, applicationIOScope)
@@ -154,11 +160,13 @@ class AppModules(
socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() },
),
serverListProvider = {
- if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) {
- TOR_ELECTRUMX_SERVERS
- } else {
- DEFAULT_ELECTRUMX_SERVERS
- }
+ // User-configured custom servers take priority
+ namecoinPrefs.customServersOrNull
+ ?: if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) {
+ TOR_ELECTRUMX_SERVERS
+ } else {
+ DEFAULT_ELECTRUMX_SERVERS
+ }
},
)
val nip05Client = Nip05Client(nip05Fetcher, namecoinResolver)
@@ -226,7 +234,7 @@ class AppModules(
val relayStats = RelayStats(client)
// Logs debug messages when needed
- val detailedLogger = if (isDebug) RelayLogger(client, false, false) else null
+ val detailedLogger = if (isDebug) RelayLogger(client, debugSending = false, debugReceiving = false) else null
val relayReqStats = if (isDebug) RelayReqStats(client) else null
val logger = if (isDebug) RelaySpeedLogger(client) else null
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
index 50b6ad326..713fe5978 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
@@ -53,8 +53,8 @@ import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
-import com.vitorpamplona.quartz.nip51Lists.relayLists.FavoriteRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
+import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent
@@ -102,7 +102,7 @@ private object PrefKeys {
const val LATEST_NIP65_RELAY_LIST = "latestNIP65RelayList"
const val LATEST_SEARCH_RELAY_LIST = "latestSearchRelayList"
const val LATEST_INDEX_RELAY_LIST = "latestIndexRelayList"
- const val LATEST_FAVORITE_RELAY_LIST = "latestFavoriteRelayList"
+ const val LATEST_RELAY_FEEDS_LIST = "latestRelayFeedsList"
const val LATEST_BLOCKED_RELAY_LIST = "latestBlockedRelayList"
const val LATEST_TRUSTED_RELAY_LIST = "latestTrustedRelayList"
const val LATEST_MUTE_LIST = "latestMuteList"
@@ -336,7 +336,7 @@ object LocalPreferences {
putOrRemove(PrefKeys.LATEST_NIP65_RELAY_LIST, settings.backupNIP65RelayList)
putOrRemove(PrefKeys.LATEST_SEARCH_RELAY_LIST, settings.backupSearchRelayList)
putOrRemove(PrefKeys.LATEST_INDEX_RELAY_LIST, settings.backupIndexRelayList)
- putOrRemove(PrefKeys.LATEST_FAVORITE_RELAY_LIST, settings.backupIndexRelayList)
+ putOrRemove(PrefKeys.LATEST_RELAY_FEEDS_LIST, settings.backupRelayFeedsList)
putOrRemove(PrefKeys.LATEST_BLOCKED_RELAY_LIST, settings.backupBlockedRelayList)
putOrRemove(PrefKeys.LATEST_TRUSTED_RELAY_LIST, settings.backupTrustedRelayList)
@@ -471,7 +471,7 @@ object LocalPreferences {
val latestNip65RelayList = parseEventOrNull(PrefKeys.LATEST_NIP65_RELAY_LIST)
val latestSearchRelayList = parseEventOrNull(PrefKeys.LATEST_SEARCH_RELAY_LIST)
val latestIndexRelayList = parseEventOrNull(PrefKeys.LATEST_INDEX_RELAY_LIST)
- val latestFavoriteRelayList = parseEventOrNull(PrefKeys.LATEST_FAVORITE_RELAY_LIST)
+ val latestRelayFeedsList = parseEventOrNull(PrefKeys.LATEST_RELAY_FEEDS_LIST)
val latestBlockedRelayList = parseEventOrNull(PrefKeys.LATEST_BLOCKED_RELAY_LIST)
val latestTrustedRelayList = parseEventOrNull(PrefKeys.LATEST_TRUSTED_RELAY_LIST)
val latestMuteList = parseEventOrNull(PrefKeys.LATEST_MUTE_LIST)
@@ -517,7 +517,7 @@ object LocalPreferences {
backupDMRelayList = latestDmRelayList,
backupSearchRelayList = latestSearchRelayList,
backupIndexRelayList = latestIndexRelayList,
- backupFavoriteRelayList = latestFavoriteRelayList,
+ backupRelayFeedsList = latestRelayFeedsList,
backupBlockedRelayList = latestBlockedRelayList,
backupTrustedRelayList = latestTrustedRelayList,
backupPrivateHomeRelayList = latestPrivateHomeRelayList,
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 3b65d75b5..055cb6ae2 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountOutboxRelayStat
import com.vitorpamplona.amethyst.model.nip01UserMetadata.NotificationInboxRelayState
import com.vitorpamplona.amethyst.model.nip01UserMetadata.UserMetadataState
import com.vitorpamplona.amethyst.model.nip02FollowLists.DeclaredFollowsPerOutboxRelay
+import com.vitorpamplona.amethyst.model.nip02FollowLists.DeclaredFollowsPerUsingRelay
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListOutboxOrProxyRelays
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowListReusedOutboxOrProxyRelays
import com.vitorpamplona.amethyst.model.nip02FollowLists.FollowsPerOutboxRelay
@@ -60,8 +61,6 @@ import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayLis
import com.vitorpamplona.amethyst.model.nip51Lists.blockedRelays.BlockedRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.broadcastRelays.BroadcastRelayListDecryptionCache
import com.vitorpamplona.amethyst.model.nip51Lists.broadcastRelays.BroadcastRelayListState
-import com.vitorpamplona.amethyst.model.nip51Lists.favoriteRelays.FavoriteRelayListDecryptionCache
-import com.vitorpamplona.amethyst.model.nip51Lists.favoriteRelays.FavoriteRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.geohashLists.GeohashListDecryptionCache
import com.vitorpamplona.amethyst.model.nip51Lists.geohashLists.GeohashListState
import com.vitorpamplona.amethyst.model.nip51Lists.hashtagLists.HashtagListDecryptionCache
@@ -76,6 +75,8 @@ import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleListDecrypti
import com.vitorpamplona.amethyst.model.nip51Lists.peopleList.PeopleListsState
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListDecryptionCache
import com.vitorpamplona.amethyst.model.nip51Lists.proxyRelays.ProxyRelayListState
+import com.vitorpamplona.amethyst.model.nip51Lists.relayFeeds.RelayFeedListState
+import com.vitorpamplona.amethyst.model.nip51Lists.relayFeeds.RelayFeedsListDecryptionCache
import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListDecryptionCache
import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListState
import com.vitorpamplona.amethyst.model.nip51Lists.trustedRelays.TrustedRelayListDecryptionCache
@@ -277,8 +278,8 @@ class Account(
val indexerRelayListDecryptionCache = IndexerRelayListDecryptionCache(signer)
val indexerRelayList = IndexerRelayListState(signer, cache, indexerRelayListDecryptionCache, scope, settings)
- val favoriteRelayListDecryptionCache = FavoriteRelayListDecryptionCache(signer)
- val favoriteRelayList = FavoriteRelayListState(signer, cache, favoriteRelayListDecryptionCache, scope, settings)
+ val relayFeedsListDecryptionCache = RelayFeedsListDecryptionCache(signer)
+ val relayFeedsList = RelayFeedListState(signer, cache, relayFeedsListDecryptionCache, scope, settings)
val blockedRelayListDecryptionCache = BlockedRelayListDecryptionCache(signer)
val blockedRelayList = BlockedRelayListState(signer, cache, blockedRelayListDecryptionCache, scope, settings)
@@ -340,7 +341,8 @@ class Account(
val defaultGlobalRelays = MergedFollowPlusMineRelayListsState(followOutboxesOrProxy, nip65RelayList, privateStorageRelayList, localRelayList, scope)
// keeps a cache of the declared outbox relays for each author
- val declaredFollowsPerRelay = DeclaredFollowsPerOutboxRelay(kind3FollowList, cache, scope).flow
+ val declaredFollowsPerOutboxRelay = DeclaredFollowsPerOutboxRelay(kind3FollowList, cache, scope).flow
+ val declaredFollowsPerUsingRelay = DeclaredFollowsPerUsingRelay(kind3FollowList, cache, scope).flow
// keeps a cache of the outbox relays for each author
val followsPerRelay = FollowsPerOutboxRelay(kind3FollowList, blockedRelayList, proxyRelayList, cache, scope).flow
@@ -933,6 +935,8 @@ class Account(
fun upgradeAttestations() = otsState.upgradeAttestationsIfNeeded(::sendAutomatic)
+ suspend fun follow(users: List) = sendMyPublicAndPrivateOutbox(kind3FollowList.follow(users))
+
suspend fun follow(user: User) = sendMyPublicAndPrivateOutbox(kind3FollowList.follow(user))
suspend fun unfollow(user: User) = sendMyPublicAndPrivateOutbox(kind3FollowList.unfollow(user))
@@ -1240,7 +1244,7 @@ class Account(
val event = signer.sign(template)
cache.justConsumeMyOwnEvent(event)
val relays = relayList(event)
- if (relays != null && relays.isNotEmpty()) {
+ if (!relays.isNullOrEmpty()) {
client.send(event, relays.toSet())
} else {
client.send(event, computeRelayListToBroadcast(event))
@@ -1898,7 +1902,7 @@ class Account(
fun getRelevantReports(note: Note): Set {
val innerReports =
if (note.event is RepostEvent || note.event is GenericRepostEvent) {
- note.replyTo?.map { getRelevantReports(it) }?.flatten() ?: emptyList()
+ note.replyTo?.flatMap { getRelevantReports(it) } ?: emptyList()
} else {
emptyList()
}
@@ -1924,11 +1928,11 @@ class Account(
suspend fun saveTrustedRelayList(trustedRelays: List) = sendMyPublicAndPrivateOutbox(trustedRelayList.saveRelayList(trustedRelays))
- suspend fun saveFavoriteRelayList(trustedRelays: List) = sendMyPublicAndPrivateOutbox(favoriteRelayList.saveRelayList(trustedRelays))
+ suspend fun saveRelayFeedsList(trustedRelays: List) = sendMyPublicAndPrivateOutbox(relayFeedsList.saveRelayList(trustedRelays))
- suspend fun followFavoriteRelay(url: NormalizedRelayUrl) = sendMyPublicAndPrivateOutbox(favoriteRelayList.addRelay(url))
+ suspend fun followRelayFeed(url: NormalizedRelayUrl) = sendMyPublicAndPrivateOutbox(relayFeedsList.addRelay(url))
- suspend fun unfollowFavoriteRelay(url: NormalizedRelayUrl) = sendMyPublicAndPrivateOutbox(favoriteRelayList.removeRelay(url))
+ suspend fun unfollowRelayFeed(url: NormalizedRelayUrl) = sendMyPublicAndPrivateOutbox(relayFeedsList.removeRelay(url))
suspend fun saveBlockedRelayList(blockedRelays: List) = sendMyPublicAndPrivateOutbox(blockedRelayList.saveRelayList(blockedRelays))
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt
index 94c178cd6..57538f5a8 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt
@@ -46,8 +46,8 @@ import com.vitorpamplona.quartz.nip51Lists.geohashList.GeohashListEvent
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent
import com.vitorpamplona.quartz.nip51Lists.muteList.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.BlockedRelayListEvent
-import com.vitorpamplona.quartz.nip51Lists.relayLists.FavoriteRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.IndexerRelayListEvent
+import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent
import com.vitorpamplona.quartz.nip51Lists.relayLists.TrustedRelayListEvent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
@@ -82,6 +82,8 @@ val DefaultNIP65List =
AdvertisedRelayInfo(Constants.bitcoiner, AdvertisedRelayType.BOTH),
)
+val DefaultGlobalRelays = listOf(Constants.wine, Constants.news)
+
val DefaultDMRelayList = listOf(Constants.auth, Constants.oxchat, Constants.nos)
val DefaultSearchRelayList = setOf(Constants.wine, Constants.where, Constants.nostoday, Constants.antiprimal, Constants.ditto)
@@ -173,7 +175,7 @@ class AccountSettings(
var backupNIP65RelayList: AdvertisedRelayListEvent? = null,
var backupSearchRelayList: SearchRelayListEvent? = null,
var backupIndexRelayList: IndexerRelayListEvent? = null,
- var backupFavoriteRelayList: FavoriteRelayListEvent? = null,
+ var backupRelayFeedsList: RelayFeedsListEvent? = null,
var backupBlockedRelayList: BlockedRelayListEvent? = null,
var backupTrustedRelayList: TrustedRelayListEvent? = null,
var backupMuteList: MuteListEvent? = null,
@@ -186,8 +188,8 @@ class AccountSettings(
var backupEphemeralChatList: EphemeralChatListEvent? = null,
var backupTrustProviderList: TrustProviderListEvent? = null,
val lastReadPerRoute: MutableStateFlow