feat: use MIP-00 KeyPackage Relay List for publish/fetch + seed default
- publishMarmotKeyPackage / publishMarmotKeyPackages now target the relays advertised in the user's kind:10051 KeyPackage Relay List (MIP-00), falling back to outbox relays when the list is empty. - fetchKeyPackageAndAddMember looks up the invitee's kind:10051 before falling back to their NIP-65 outbox. - Add kind:10051 to user metadata + account info subscription filters so invitees' KeyPackage relay lists are cached in time for invites. - Seed a default kind:10051 on signup from the default NIP-65 relay set and publish it alongside other account bootstrap events. - Warn the user when creating a Marmot group without a kind:10051 list and offer to create one from their current outbox relays. - Sync overload for KeyPackageRelayListEvent.create to support the signup-time temp signer. https://claude.ai/code/session_01B7kTUFAPvWarWd6fvQKNBy
This commit is contained in:
@@ -1788,23 +1788,32 @@ class Account(
|
|||||||
// Build filter for the member's KeyPackages
|
// Build filter for the member's KeyPackages
|
||||||
val filter = manager.subscriptionManager.keyPackageFilter(memberPubKey)
|
val filter = manager.subscriptionManager.keyPackageFilter(memberPubKey)
|
||||||
|
|
||||||
// KeyPackages are published by the invitee to *their own* outbox
|
// Per MIP-00, invitees advertise the relays that host their
|
||||||
// (see publishMarmotKeyPackage), so look there first. Union with
|
// KeyPackages in a kind:10051 KeyPackageRelayListEvent. Look
|
||||||
// our own outbox so we still find packages that ended up on a
|
// there first, then fall back to the invitee's NIP-65 outbox
|
||||||
// shared relay, and as a fallback when we don't know the
|
// (where KeyPackages typically also land), and finally union
|
||||||
// invitee's outbox yet.
|
// with our own outbox so we still find packages that ended up
|
||||||
|
// on a shared relay.
|
||||||
val myOutbox = outboxRelays.flow.value
|
val myOutbox = outboxRelays.flow.value
|
||||||
|
val memberKeyPackageRelays =
|
||||||
|
(
|
||||||
|
cache
|
||||||
|
.getAddressableNoteIfExists(
|
||||||
|
com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||||
|
.createAddress(memberPubKey),
|
||||||
|
)?.event as? com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||||
|
)?.relays()?.toSet().orEmpty()
|
||||||
val memberOutbox =
|
val memberOutbox =
|
||||||
cache
|
cache
|
||||||
.getOrCreateUser(memberPubKey)
|
.getOrCreateUser(memberPubKey)
|
||||||
.outboxRelays()
|
.outboxRelays()
|
||||||
?.toSet()
|
?.toSet()
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
val fetchRelays = memberOutbox + myOutbox
|
val fetchRelays = memberKeyPackageRelays + memberOutbox + myOutbox
|
||||||
|
|
||||||
Log.d("MarmotDbg") {
|
Log.d("MarmotDbg") {
|
||||||
"fetchKeyPackageAndAddMember: querying ${fetchRelays.size} relay(s) for ${memberPubKey.take(8)}… KeyPackage " +
|
"fetchKeyPackageAndAddMember: querying ${fetchRelays.size} relay(s) for ${memberPubKey.take(8)}… KeyPackage " +
|
||||||
"(memberOutbox=${memberOutbox.size}, myOutbox=${myOutbox.size}): ${fetchRelays.map { it.url }}"
|
"(memberKeyPackageRelays=${memberKeyPackageRelays.size}, memberOutbox=${memberOutbox.size}, myOutbox=${myOutbox.size}): ${fetchRelays.map { it.url }}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query across the combined relay set
|
// Query across the combined relay set
|
||||||
@@ -1939,6 +1948,20 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relays where this account publishes kind:30443 KeyPackage events.
|
||||||
|
*
|
||||||
|
* Per MIP-00, these should match the relays advertised in the user's
|
||||||
|
* kind:10051 KeyPackage Relay List so that other clients can discover
|
||||||
|
* and fetch them. Falls back to the standard outbox set when no list
|
||||||
|
* has been configured yet, since that's also where the user's other
|
||||||
|
* write-oriented events land.
|
||||||
|
*/
|
||||||
|
fun keyPackagePublishRelays(): Set<NormalizedRelayUrl> {
|
||||||
|
val list = keyPackageRelayList.flow.value
|
||||||
|
return if (list.isNotEmpty()) list else outboxRelays.flow.value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publish or rotate KeyPackage events.
|
* Publish or rotate KeyPackage events.
|
||||||
*/
|
*/
|
||||||
@@ -1946,13 +1969,13 @@ class Account(
|
|||||||
val manager = marmotManager ?: return
|
val manager = marmotManager ?: return
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val relays = outboxRelays.flow.value.toList()
|
val relays = keyPackagePublishRelays()
|
||||||
|
|
||||||
if (manager.needsKeyPackageRotation()) {
|
if (manager.needsKeyPackageRotation()) {
|
||||||
val rotatedEvents = manager.rotateConsumedKeyPackages(relays)
|
val rotatedEvents = manager.rotateConsumedKeyPackages(relays.toList())
|
||||||
rotatedEvents.forEach { event ->
|
rotatedEvents.forEach { event ->
|
||||||
cache.justConsumeMyOwnEvent(event)
|
cache.justConsumeMyOwnEvent(event)
|
||||||
client.publish(event, outboxRelays.flow.value)
|
client.publish(event, relays)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1964,16 +1987,16 @@ class Account(
|
|||||||
val manager = marmotManager ?: return
|
val manager = marmotManager ?: return
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val relays = outboxRelays.flow.value.toList()
|
val relays = keyPackagePublishRelays()
|
||||||
Log.d("MarmotDbg") {
|
Log.d("MarmotDbg") {
|
||||||
"publishMarmotKeyPackage: generating + publishing KeyPackage event → ${relays.size} relay(s): ${relays.map { it.url }}"
|
"publishMarmotKeyPackage: generating + publishing KeyPackage event → ${relays.size} relay(s): ${relays.map { it.url }}"
|
||||||
}
|
}
|
||||||
val event = manager.generateKeyPackageEvent(relays)
|
val event = manager.generateKeyPackageEvent(relays.toList())
|
||||||
Log.d("MarmotDbg") {
|
Log.d("MarmotDbg") {
|
||||||
"publishMarmotKeyPackage: signed kind:${event.kind} id=${event.id.take(8)}… authored=${event.pubKey.take(8)}…"
|
"publishMarmotKeyPackage: signed kind:${event.kind} id=${event.id.take(8)}… authored=${event.pubKey.take(8)}…"
|
||||||
}
|
}
|
||||||
cache.justConsumeMyOwnEvent(event)
|
cache.justConsumeMyOwnEvent(event)
|
||||||
client.publish(event, outboxRelays.flow.value)
|
client.publish(event, relays)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+2
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.metada
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip78AppSpecific.AppSpecificState.Companion.APP_SPECIFIC_DATA_D_TAG
|
import com.vitorpamplona.amethyst.model.nip78AppSpecific.AppSpecificState.Companion.APP_SPECIFIC_DATA_D_TAG
|
||||||
import com.vitorpamplona.quartz.experimental.nipA3.PaymentTargetsEvent
|
import com.vitorpamplona.quartz.experimental.nipA3.PaymentTargetsEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
@@ -53,6 +54,7 @@ val AccountInfoAndListsFromKeyKinds =
|
|||||||
StatusEvent.KIND,
|
StatusEvent.KIND,
|
||||||
AdvertisedRelayListEvent.KIND,
|
AdvertisedRelayListEvent.KIND,
|
||||||
ChatMessageRelayListEvent.KIND,
|
ChatMessageRelayListEvent.KIND,
|
||||||
|
KeyPackageRelayListEvent.KIND,
|
||||||
SearchRelayListEvent.KIND,
|
SearchRelayListEvent.KIND,
|
||||||
FileServersEvent.KIND,
|
FileServersEvent.KIND,
|
||||||
BlossomServersEvent.KIND,
|
BlossomServersEvent.KIND,
|
||||||
|
|||||||
+2
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.metadata
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.metadata
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
@@ -47,6 +48,7 @@ val BasicAccountInfoKinds =
|
|||||||
ContactListEvent.KIND,
|
ContactListEvent.KIND,
|
||||||
AdvertisedRelayListEvent.KIND,
|
AdvertisedRelayListEvent.KIND,
|
||||||
ChatMessageRelayListEvent.KIND,
|
ChatMessageRelayListEvent.KIND,
|
||||||
|
KeyPackageRelayListEvent.KIND,
|
||||||
SearchRelayListEvent.KIND,
|
SearchRelayListEvent.KIND,
|
||||||
FileServersEvent.KIND,
|
FileServersEvent.KIND,
|
||||||
BlossomServersEvent.KIND,
|
BlossomServersEvent.KIND,
|
||||||
|
|||||||
+2
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
@@ -41,6 +42,7 @@ val UserMetadataForKeyKinds =
|
|||||||
StatusEvent.KIND,
|
StatusEvent.KIND,
|
||||||
AdvertisedRelayListEvent.KIND,
|
AdvertisedRelayListEvent.KIND,
|
||||||
ChatMessageRelayListEvent.KIND,
|
ChatMessageRelayListEvent.KIND,
|
||||||
|
KeyPackageRelayListEvent.KIND,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun filterUserMetadataForKey(
|
fun filterUserMetadataForKey(
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.model.DefaultNIP65RelaySet
|
|||||||
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
|
import com.vitorpamplona.amethyst.model.DefaultSearchRelayList
|
||||||
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
@@ -293,6 +294,7 @@ class AccountSessionManager(
|
|||||||
accountSettings.backupContactList?.let { client.publish(it, toPost) }
|
accountSettings.backupContactList?.let { client.publish(it, toPost) }
|
||||||
accountSettings.backupNIP65RelayList?.let { client.publish(it, toPost) }
|
accountSettings.backupNIP65RelayList?.let { client.publish(it, toPost) }
|
||||||
accountSettings.backupDMRelayList?.let { client.publish(it, toPost) }
|
accountSettings.backupDMRelayList?.let { client.publish(it, toPost) }
|
||||||
|
accountSettings.backupKeyPackageRelayList?.let { client.publish(it, toPost) }
|
||||||
accountSettings.backupSearchRelayList?.let { client.publish(it, toPost) }
|
accountSettings.backupSearchRelayList?.let { client.publish(it, toPost) }
|
||||||
accountSettings.backupIndexRelayList?.let { client.publish(it, toPost) }
|
accountSettings.backupIndexRelayList?.let { client.publish(it, toPost) }
|
||||||
accountSettings.backupRelayFeedsList?.let { client.publish(it, toPost) }
|
accountSettings.backupRelayFeedsList?.let { client.publish(it, toPost) }
|
||||||
@@ -317,6 +319,10 @@ class AccountSessionManager(
|
|||||||
),
|
),
|
||||||
backupNIP65RelayList = AdvertisedRelayListEvent.create(DefaultNIP65List, tempSigner),
|
backupNIP65RelayList = AdvertisedRelayListEvent.create(DefaultNIP65List, tempSigner),
|
||||||
backupDMRelayList = ChatMessageRelayListEvent.create(DefaultDMRelayList, tempSigner),
|
backupDMRelayList = ChatMessageRelayListEvent.create(DefaultDMRelayList, tempSigner),
|
||||||
|
// MIP-00: advertise the default outbox relays as KeyPackage hosts
|
||||||
|
// so other users can discover and fetch this account's KeyPackage
|
||||||
|
// events without having to guess where they were published.
|
||||||
|
backupKeyPackageRelayList = KeyPackageRelayListEvent.create(DefaultNIP65RelaySet.toList(), tempSigner),
|
||||||
backupSearchRelayList = SearchRelayListEvent.create(DefaultSearchRelayList.toList(), tempSigner),
|
backupSearchRelayList = SearchRelayListEvent.create(DefaultSearchRelayList.toList(), tempSigner),
|
||||||
backupIndexRelayList = IndexerRelayListEvent.create(DefaultIndexerRelayList.toList(), tempSigner),
|
backupIndexRelayList = IndexerRelayListEvent.create(DefaultIndexerRelayList.toList(), tempSigner),
|
||||||
backupChannelList = ChannelListEvent.create(emptyList(), DefaultChannels, tempSigner),
|
backupChannelList = ChannelListEvent.create(emptyList(), DefaultChannels, tempSigner),
|
||||||
|
|||||||
+21
@@ -1471,6 +1471,27 @@ class AccountViewModel(
|
|||||||
|
|
||||||
suspend fun hasPublishedKeyPackage(): Boolean = account.hasPublishedKeyPackage()
|
suspend fun hasPublishedKeyPackage(): Boolean = account.hasPublishedKeyPackage()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this account has a kind:10051 KeyPackage Relay List (MIP-00)
|
||||||
|
* advertising where it publishes KeyPackages.
|
||||||
|
*/
|
||||||
|
fun hasKeyPackageRelayList(): Boolean =
|
||||||
|
account.keyPackageRelayList.flow.value
|
||||||
|
.isNotEmpty()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes a kind:10051 KeyPackage Relay List seeded from the account's
|
||||||
|
* current outbox relays. Used when the user opts in from the
|
||||||
|
* "Create Group" warning dialog.
|
||||||
|
*/
|
||||||
|
suspend fun saveKeyPackageRelayListFromOutbox() {
|
||||||
|
val outbox =
|
||||||
|
account.outboxRelays.flow.value
|
||||||
|
.toList()
|
||||||
|
if (outbox.isEmpty()) return
|
||||||
|
account.saveKeyPackageRelayList(outbox)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun leaveMarmotGroup(nostrGroupId: String) {
|
suspend fun leaveMarmotGroup(nostrGroupId: String) {
|
||||||
val relays = marmotGroupRelays(nostrGroupId)
|
val relays = marmotGroupRelays(nostrGroupId)
|
||||||
account.leaveMarmotGroup(nostrGroupId, relays)
|
account.leaveMarmotGroup(nostrGroupId, relays)
|
||||||
|
|||||||
+90
-30
@@ -26,10 +26,12 @@ import androidx.compose.foundation.layout.consumeWindowInsets
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -55,44 +57,53 @@ fun CreateGroupScreen(
|
|||||||
) {
|
) {
|
||||||
var groupName by remember { mutableStateOf("") }
|
var groupName by remember { mutableStateOf("") }
|
||||||
var isCreating by remember { mutableStateOf(false) }
|
var isCreating by remember { mutableStateOf(false) }
|
||||||
|
var showKeyPackageRelayDialog by remember { mutableStateOf(false) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
fun proceedWithCreate() {
|
||||||
|
isCreating = true
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val nostrGroupId = RandomInstance.bytes(32).toHexKey()
|
||||||
|
accountViewModel.createMarmotGroup(nostrGroupId)
|
||||||
|
// Always commit an initial metadata extension so that
|
||||||
|
// (a) the name (if any) is persisted in MLS extensions
|
||||||
|
// and survives app restarts,
|
||||||
|
// (b) the inviter's outbox relays land in the group
|
||||||
|
// metadata so every member ends up with the same
|
||||||
|
// canonical relay set for kind:445 — without this,
|
||||||
|
// invitees would never receive the group's messages.
|
||||||
|
// Both are handled inside `updateMarmotGroupMetadata`.
|
||||||
|
accountViewModel.updateMarmotGroupMetadata(
|
||||||
|
nostrGroupId = nostrGroupId,
|
||||||
|
name = groupName.trim(),
|
||||||
|
description = "",
|
||||||
|
)
|
||||||
|
nav.nav(Route.MarmotGroupChat(nostrGroupId))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
isCreating = false
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
"Failed to create group: ${e.message}",
|
||||||
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
CreatingTopBar(
|
CreatingTopBar(
|
||||||
onCancel = { nav.popBack() },
|
onCancel = { nav.popBack() },
|
||||||
onPost = {
|
onPost = {
|
||||||
isCreating = true
|
if (!accountViewModel.hasKeyPackageRelayList()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
showKeyPackageRelayDialog = true
|
||||||
try {
|
} else {
|
||||||
val nostrGroupId = RandomInstance.bytes(32).toHexKey()
|
proceedWithCreate()
|
||||||
accountViewModel.createMarmotGroup(nostrGroupId)
|
|
||||||
// Always commit an initial metadata extension so that
|
|
||||||
// (a) the name (if any) is persisted in MLS extensions
|
|
||||||
// and survives app restarts,
|
|
||||||
// (b) the inviter's outbox relays land in the group
|
|
||||||
// metadata so every member ends up with the same
|
|
||||||
// canonical relay set for kind:445 — without this,
|
|
||||||
// invitees would never receive the group's messages.
|
|
||||||
// Both are handled inside `updateMarmotGroupMetadata`.
|
|
||||||
accountViewModel.updateMarmotGroupMetadata(
|
|
||||||
nostrGroupId = nostrGroupId,
|
|
||||||
name = groupName.trim(),
|
|
||||||
description = "",
|
|
||||||
)
|
|
||||||
nav.nav(Route.MarmotGroupChat(nostrGroupId))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
isCreating = false
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
"Failed to create group: ${e.message}",
|
|
||||||
Toast.LENGTH_LONG,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isActive = { !isCreating },
|
isActive = { !isCreating },
|
||||||
@@ -129,4 +140,53 @@ fun CreateGroupScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showKeyPackageRelayDialog) {
|
||||||
|
MissingKeyPackageRelayListDialog(
|
||||||
|
onConfirm = {
|
||||||
|
showKeyPackageRelayDialog = false
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.saveKeyPackageRelayListFromOutbox()
|
||||||
|
}
|
||||||
|
proceedWithCreate()
|
||||||
|
},
|
||||||
|
onDismiss = {
|
||||||
|
showKeyPackageRelayDialog = false
|
||||||
|
proceedWithCreate()
|
||||||
|
},
|
||||||
|
onCancel = {
|
||||||
|
showKeyPackageRelayDialog = false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MissingKeyPackageRelayListDialog(
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onCancel: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onCancel,
|
||||||
|
title = { Text("KeyPackage Relays not set") },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
"You don't have a KeyPackage Relay List yet (MIP-00). " +
|
||||||
|
"This list tells other people where your KeyPackage is " +
|
||||||
|
"published so they can invite you to group chats.\n\n" +
|
||||||
|
"Use your current outbox relays for this?",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onConfirm) {
|
||||||
|
Text("Use outbox relays")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("Skip for now")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -27,6 +27,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.isLocalHost
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.isLocalHost
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||||
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
@@ -70,6 +71,12 @@ class KeyPackageRelayListEvent(
|
|||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
): KeyPackageRelayListEvent = signer.sign(createdAt, KIND, createTagArray(relays), "")
|
): KeyPackageRelayListEvent = signer.sign(createdAt, KIND, createTagArray(relays), "")
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
relays: List<NormalizedRelayUrl>,
|
||||||
|
signer: NostrSignerSync,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
): KeyPackageRelayListEvent = signer.sign(createdAt, KIND, createTagArray(relays), "")
|
||||||
|
|
||||||
suspend fun updateRelayList(
|
suspend fun updateRelayList(
|
||||||
earlierVersion: KeyPackageRelayListEvent,
|
earlierVersion: KeyPackageRelayListEvent,
|
||||||
relays: List<NormalizedRelayUrl>,
|
relays: List<NormalizedRelayUrl>,
|
||||||
|
|||||||
Reference in New Issue
Block a user