diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 000000000..4a53bee8c
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index e0da3ee56..bb270b80a 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -65,6 +65,10 @@
+
+
+
+
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt
index d1f891f2b..b83a2c3dc 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt
@@ -142,6 +142,7 @@ class Amethyst : Application() {
RECEIVER_EXPORTED,
)
} else {
+ @Suppress("UnspecifiedRegisterReceiverFlag")
registerReceiver(
pokeyReceiver,
IntentFilter(PokeyReceiver.POKEY_ACTION),
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 33ced9e5d..a965161d4 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
@@ -150,6 +150,7 @@ import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
+import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
@@ -1618,18 +1619,18 @@ class Account(
suspend fun report(
note: Note,
- type: ReportEvent.ReportType,
+ type: ReportType,
content: String = "",
) {
if (!isWriteable()) return
- if (note.hasReacted(userProfile(), "⚠️")) {
- // has already liked this note
+ if (note.hasReport(userProfile(), type)) {
+ // has already reported this note
return
}
note.event?.let {
- ReportEvent.create(it, type, signer, content) {
+ signer.sign(ReportEvent.build(it, type)) {
Amethyst.instance.client.send(it)
LocalCache.justConsume(it, null)
}
@@ -1638,7 +1639,7 @@ class Account(
suspend fun report(
user: User,
- type: ReportEvent.ReportType,
+ type: ReportType,
) {
if (!isWriteable()) return
@@ -1647,7 +1648,8 @@ class Account(
return
}
- ReportEvent.create(user.pubkeyHex, type, signer) {
+ val template = ReportEvent.build(user.pubkeyHex, type)
+ signer.sign(template) {
Amethyst.instance.client.send(it)
LocalCache.justConsume(it, null)
}
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 f36b2498a..048aabbd6 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
@@ -695,8 +695,8 @@ object LocalCache {
event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
is ReportEvent ->
- event.reportedPost().mapNotNull { checkGetOrCreateNote(it.key) } +
- event.taggedAddresses().map { getOrCreateAddressableNote(it) }
+ event.reportedPost().mapNotNull { checkGetOrCreateNote(it.eventId) } +
+ event.reportedAddresses().map { getOrCreateAddressableNote(it.address) }
is ChannelMessageEvent ->
event
.tagsWithoutCitations()
@@ -1342,7 +1342,7 @@ object LocalCache {
// Already processed this event.
if (note.event != null) return
- val mentions = event.reportedAuthor().mapNotNull { checkGetOrCreateUser(it.key) }
+ val mentions = event.reportedAuthor().mapNotNull { checkGetOrCreateUser(it.pubkey) }
val repliesTo = computeReplyTo(event)
note.loadEvent(event, author, repliesTo)
@@ -2231,7 +2231,7 @@ object LocalCache {
}
if (noteEvent is ReportEvent) {
noteEvent.reportedAuthor().mapNotNull {
- val author = getUserIfExists(it.key)
+ val author = getUserIfExists(it.pubkey)
author?.removeReport(note)
author?.clearEOSE()
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt
new file mode 100644
index 000000000..6790b244a
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/MediaAspectRatioCache.kt
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.model
+
+import android.util.LruCache
+
+object MediaAspectRatioCache {
+ val mediaAspectRatioCacheByUrl = LruCache(1000)
+
+ fun get(url: String) = mediaAspectRatioCacheByUrl.get(url)
+
+ fun add(
+ url: String,
+ width: Int,
+ height: Int,
+ ) {
+ if (height > 1) {
+ mediaAspectRatioCacheByUrl.put(url, width.toFloat() / height.toFloat())
+ }
+ }
+}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
index 9868922ca..984781e74 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
@@ -68,6 +68,8 @@ import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
+import com.vitorpamplona.quartz.nip56Reports.ReportEvent
+import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
@@ -717,6 +719,15 @@ open class Note(
)
}
+ fun hasReport(
+ loggedIn: User,
+ type: ReportType,
+ ): Boolean =
+ reports[loggedIn]?.firstOrNull {
+ it.event is ReportEvent &&
+ (it.event as ReportEvent).reportedAuthor().any { it.type == type }
+ } != null
+
fun hasPledgeBy(user: User): Boolean =
replies
.filter { it.event?.hasAdditionalReward() ?: false }
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt
index e033aadfa..a1c242923 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/User.kt
@@ -46,6 +46,7 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
+import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.utils.DualCase
@@ -363,11 +364,11 @@ class User(
fun hasReport(
loggedIn: User,
- type: ReportEvent.ReportType,
+ type: ReportType,
): Boolean =
reports[loggedIn]?.firstOrNull {
it.event is ReportEvent &&
- (it.event as ReportEvent).reportedAuthor().any { it.reportType == type }
+ (it.event as ReportEvent).reportedAuthor().any { it.type == type }
} != null
fun containsAny(hiddenWordsCase: List): Boolean {
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt
index 55a13f3b6..cc5b30f6b 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CachedRichTextParser.kt
@@ -41,6 +41,12 @@ object CachedRichTextParser {
return result
}
+ fun cachedText(
+ content: String,
+ tags: ImmutableListOfLists,
+ callbackUri: String? = null,
+ ): RichTextViewerState? = richTextCache[hashCodeCache(content, tags, callbackUri)]
+
fun parseText(
content: String,
tags: ImmutableListOfLists,
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt
index 40ecb84b1..b212ccce1 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.service.lnurl
import android.content.Context
+import android.util.Log
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.R
@@ -166,12 +167,24 @@ class LightningAddressResolver {
response: Response,
context: Context,
): String {
+ val body = response.body.string()
+
val errorMessage =
runCatching {
- jacksonObjectMapper().readTree(response.body.string())
+ jacksonObjectMapper().readTree(body)
}.getOrNull()?.let { tree ->
- val status = tree.get("status")?.asText()
- val message = tree.get("message")?.asText()
+ val errorNode = tree.get("error")
+ val messageNode = tree.get("message")
+ val statusNode = tree.get("status")
+
+ if (tree.get("error").isBoolean && messageNode != null) {
+ if (errorNode.asBoolean() == true) {
+ return messageNode.asText()
+ }
+ }
+
+ val status = statusNode?.asText()
+ val message = messageNode?.asText()
if (status == "error" && message != null) {
message
@@ -180,6 +193,10 @@ class LightningAddressResolver {
}
}
+ if (errorMessage == null) {
+ Log.d("LightningAddressResolver", "Error parsing LNResponse: $body")
+ }
+
return errorMessage
?: HttpStatusMessages.resourceIdFor(response.code)?.let { stringRes(context, it) }
?: response.message.ifBlank { null }
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt
index f04170dc1..80989981a 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/MultiPlayerPlaybackManager.kt
@@ -30,9 +30,11 @@ import androidx.media3.common.Player
import androidx.media3.common.Player.PositionInfo
import androidx.media3.common.Player.STATE_IDLE
import androidx.media3.common.Player.STATE_READY
+import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
+import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
import com.vitorpamplona.amethyst.ui.MainActivity
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
@@ -113,6 +115,10 @@ class MultiPlayerPlaybackManager(
// avoids saving positions for live streams otherwise caching goes crazy
val mustCachePositions = !uri.contains(".m3u8", true)
+ override fun onVideoSizeChanged(videoSize: VideoSize) {
+ MediaAspectRatioCache.add(uri, videoSize.width, videoSize.height)
+ }
+
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying) {
player.setWakeMode(C.WAKE_MODE_NETWORK)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt
index 61f7e1a31..400c1bb40 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt
@@ -331,7 +331,7 @@ fun EditPostView(
it,
accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality ->
- postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel::toast, context)
+ postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
}
@@ -365,7 +365,7 @@ fun EditPostView(
postViewModel.wantsInvoice = false
},
onClose = { postViewModel.wantsInvoice = false },
- onError = { title, message -> accountViewModel.toast(title, message) },
+ onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
index 833417c6e..4bee43e17 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt
@@ -140,7 +140,7 @@ fun NewMediaView(
PostButton(
onPost = {
- postViewModel.upload(context, relayList, onClose, accountViewModel::toast)
+ postViewModel.upload(context, relayList, onClose, accountViewModel.toastManager::toast)
postViewModel.selectedServer?.let {
if (it.type != ServerType.NIP95) {
account.settings.changeDefaultFileServer(it)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt
index 26ee67343..2b3fc3378 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt
@@ -198,7 +198,7 @@ fun NewUserMetadataScreen(
tint = MaterialTheme.colorScheme.placeholderText,
modifier = Modifier.padding(start = 5.dp),
) {
- postViewModel.uploadForPicture(it, context, onError = accountViewModel::toast)
+ postViewModel.uploadForPicture(it, context, onError = accountViewModel.toastManager::toast)
}
},
singleLine = true,
@@ -223,7 +223,7 @@ fun NewUserMetadataScreen(
tint = MaterialTheme.colorScheme.placeholderText,
modifier = Modifier.padding(start = 5.dp),
) {
- postViewModel.uploadForBanner(it, context, onError = accountViewModel::toast)
+ postViewModel.uploadForBanner(it, context, onError = accountViewModel.toastManager::toast)
}
},
singleLine = true,
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt
index 21330a85a..ed2b4b4ef 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt
@@ -218,7 +218,7 @@ fun RelaySelectionDialog(
)
}
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.unable_to_download_relay_document),
msg,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt
index b6255abf8..4cc08ed1c 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt
@@ -103,7 +103,7 @@ fun BasicRelaySetupInfoDialog(
)
}
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.unable_to_download_relay_document),
msg,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt
index 0f850e99f..5a3a1d357 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt
@@ -280,7 +280,7 @@ fun LoadRelayInfo(
)
}
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.unable_to_download_relay_document),
msg,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalDialog.kt
index 4f31f931e..b767d58aa 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalDialog.kt
@@ -105,7 +105,7 @@ fun Kind3RelaySetupInfoProposalDialog(
)
}
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.unable_to_download_relay_document),
msg,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt
index cc962f34e..d5707a366 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt
@@ -64,7 +64,7 @@ fun RelayStatusRow(
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_from_relay,
R.string.read_from_relay_description,
)
@@ -91,7 +91,7 @@ fun RelayStatusRow(
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.write_to_relay,
R.string.write_to_relay_description,
)
@@ -118,7 +118,7 @@ fun RelayStatusRow(
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.errors,
R.string.errors_description,
)
@@ -150,7 +150,7 @@ fun RelayStatusRow(
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.spam,
R.string.spam_description,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt
index d50594d3c..3a8303793 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt
@@ -116,7 +116,7 @@ fun CashuPreview(
CashuPreviewNew(
it,
accountViewModel::meltCashu,
- accountViewModel::toast,
+ accountViewModel.toastManager::toast,
)
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadingAnimation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadingAnimation.kt
index c1245cbff..1e4f2176c 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadingAnimation.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/LoadingAnimation.kt
@@ -59,6 +59,7 @@ val DefaultAnimationColors =
@Composable
fun LoadingAnimation(
indicatorSize: Dp = 20.dp,
+ circleWidth: Dp = 4.dp,
circleColors: ImmutableList = DefaultAnimationColors,
animationDuration: Int = 1000,
) {
@@ -86,7 +87,7 @@ fun LoadingAnimation(
.size(size = indicatorSize)
.rotate(degrees = rotateAnimation)
.border(
- width = 4.dp,
+ width = circleWidth,
brush = Brush.sweepGradient(circleColors),
shape = CircleShape,
),
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt
index 1a5d99cb7..0320f5086 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt
@@ -530,20 +530,22 @@ fun DisplaySecretEmoji(
) {
if (canPreview && quotesLeft > 0) {
var secretContent by remember {
- mutableStateOf(null)
+ mutableStateOf(CachedRichTextParser.cachedText(EmojiCoder.decode(segment.segmentText), state.tags))
}
var showPopup by remember {
mutableStateOf(false)
}
- LaunchedEffect(segment) {
- launch(Dispatchers.Default) {
- secretContent =
- CachedRichTextParser.parseText(
- EmojiCoder.decode(segment.segmentText),
- state.tags,
- )
+ if (secretContent == null) {
+ LaunchedEffect(segment) {
+ launch(Dispatchers.Default) {
+ secretContent =
+ CachedRichTextParser.parseText(
+ EmojiCoder.decode(segment.segmentText),
+ state.tags,
+ )
+ }
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt
index 0fbc34067..3f127dba5 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt
@@ -98,6 +98,7 @@ import com.linc.audiowaveform.infiniteLinearGradient
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache
import com.vitorpamplona.amethyst.commons.compose.produceCachedState
+import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
import com.vitorpamplona.amethyst.service.playback.PlaybackClientController
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
@@ -261,7 +262,8 @@ fun VideoView(
}
if (blurhash == null) {
- val ratio = dimensions?.aspectRatio()
+ val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri)
+
val modifier =
if (ratio != null && automaticallyStartPlayback.value) {
Modifier.aspectRatio(ratio)
@@ -295,7 +297,7 @@ fun VideoView(
}
}
} else {
- val ratio = dimensions?.aspectRatio()
+ val ratio = dimensions?.aspectRatio() ?: MediaAspectRatioCache.get(videoUri)
val modifier =
if (ratio != null) {
@@ -1233,10 +1235,10 @@ private fun saveMediaToGallery(
mimeType = mimeType,
localContext = localContext,
onSuccess = {
- accountViewModel.toast(R.string.video_saved_to_the_gallery, R.string.video_saved_to_the_gallery)
+ accountViewModel.toastManager.toast(R.string.video_saved_to_the_gallery, R.string.video_saved_to_the_gallery)
},
onError = {
- accountViewModel.toast(R.string.failed_to_save_the_video, null, it)
+ accountViewModel.toastManager.toast(R.string.failed_to_save_the_video, null, it)
},
)
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt
index c99dc3cb6..fa770082a 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentDialog.kt
@@ -312,10 +312,10 @@ private fun saveMediaToGallery(
forceProxy = useTor,
localContext,
onSuccess = {
- accountViewModel.toast(success, success)
+ accountViewModel.toastManager.toast(success, success)
},
onError = {
- accountViewModel.toast(failure, null, it)
+ accountViewModel.toastManager.toast(failure, null, it)
},
)
} else if (content is MediaPreloadedContent) {
@@ -325,10 +325,10 @@ private fun saveMediaToGallery(
content.mimeType,
localContext,
onSuccess = {
- accountViewModel.toast(success, success)
+ accountViewModel.toastManager.toast(success, success)
},
onError = { innerIt ->
- accountViewModel.toast(failure, null, innerIt)
+ accountViewModel.toastManager.toast(failure, null, innerIt)
},
)
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
index 378a2dddc..3520fe829 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt
@@ -43,6 +43,7 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -77,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaPreloadedContent
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
+import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
import com.vitorpamplona.amethyst.service.Blurhash
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
@@ -87,10 +89,13 @@ import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon
import com.vitorpamplona.amethyst.ui.note.HashCheckFailedIcon
import com.vitorpamplona.amethyst.ui.note.HashCheckIcon
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
+import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.UrlImageView
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.Size24dp
import com.vitorpamplona.amethyst.ui.theme.Size30dp
+import com.vitorpamplona.amethyst.ui.theme.Size40dp
+import com.vitorpamplona.amethyst.ui.theme.Size6dp
import com.vitorpamplona.amethyst.ui.theme.Size75dp
import com.vitorpamplona.amethyst.ui.theme.hashVerifierMark
import com.vitorpamplona.amethyst.ui.theme.imageModifier
@@ -132,7 +137,6 @@ fun ZoomableContentView(
.fillMaxWidth()
.clickable { dialogOpen = true }
val loadedImageModifier = if (roundedCorner) MaterialTheme.colorScheme.imageModifier else Modifier.fillMaxWidth()
-
UrlImageView(content, contentScale, mainImageModifier, loadedImageModifier, controllerVisible, accountViewModel = accountViewModel)
}
}
@@ -235,7 +239,7 @@ fun LocalImageView(
)
}
- val ratio = remember(content) { aspectRatio(content.dim) }
+ val ratio = remember(content) { content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.localFile.toString()) }
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) {
if (it) {
SubcomposeAsyncImage(
@@ -265,8 +269,14 @@ fun LocalImageView(
)
}
} else {
- WaitAndDisplay {
- DisplayUrlWithLoadingSymbol(content)
+ if (ratio != null) {
+ Box(loadedImageModifier.aspectRatio(ratio), contentAlignment = Alignment.Center) {
+ LoadingAnimation(Size40dp, Size6dp)
+ }
+ } else {
+ WaitAndDisplay {
+ DisplayUrlWithLoadingSymbol(content)
+ }
}
}
}
@@ -276,6 +286,11 @@ fun LocalImageView(
is AsyncImagePainter.State.Success -> {
SubcomposeAsyncImageContent(loadedImageModifier)
+ SideEffect {
+ val drawable = (state as AsyncImagePainter.State.Success).result.image
+ MediaAspectRatioCache.add(content.localFile.toString(), drawable.width, drawable.height)
+ }
+
content.isVerified?.let {
AnimatedVisibility(
visible = controllerVisible.value,
@@ -328,7 +343,7 @@ fun UrlImageView(
accountViewModel: AccountViewModel,
alwayShowImage: Boolean = false,
) {
- val ratio = content.dim?.aspectRatio()
+ val ratio = content.dim?.aspectRatio() ?: MediaAspectRatioCache.get(content.url)
val showImage =
remember {
@@ -373,8 +388,14 @@ fun UrlImageView(
)
}
} else {
- WaitAndDisplay {
- DisplayUrlWithLoadingSymbol(content)
+ if (ratio != null) {
+ Box(loadedImageModifier.aspectRatio(ratio), contentAlignment = Alignment.Center) {
+ LoadingAnimation(Size40dp, Size6dp)
+ }
+ } else {
+ WaitAndDisplay {
+ DisplayUrlWithLoadingSymbol(content)
+ }
}
}
}
@@ -385,6 +406,11 @@ fun UrlImageView(
SubcomposeAsyncImageContent(loadedImageModifier)
ShowHashAnimated(content, controllerVisible, Modifier.align(Alignment.TopEnd))
+
+ SideEffect {
+ val drawable = (state as AsyncImagePainter.State.Success).result.image
+ MediaAspectRatioCache.add(content.url, drawable.width, drawable.height)
+ }
}
else -> {}
}
@@ -519,12 +545,6 @@ fun ShowHash(content: MediaUrlContent) {
verifiedHash?.let { HashVerificationSymbol(it) }
}
-fun aspectRatio(dim: DimensionTag?): Float? {
- if (dim == null) return null
-
- return dim.width.toFloat() / dim.height.toFloat()
-}
-
@Composable
fun WaitAndDisplay(
content:
@@ -706,7 +726,7 @@ fun ShareImageAction(
val n19 = Nip19Parser.uriToRoute(postNostrUri)?.entity as? NEvent
if (n19 != null) {
accountViewModel.addMediaToGallery(n19.hex, videoUri, n19.relay.getOrNull(0), blurhash, dim, hash, mimeType) // TODO Whole list or first?
- accountViewModel.toast(R.string.media_added, R.string.media_added_to_profile_gallery)
+ accountViewModel.toastManager.toast(R.string.media_added, R.string.media_added_to_profile_gallery)
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/DisplayErrorMessages.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/DisplayErrorMessages.kt
similarity index 75%
rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/DisplayErrorMessages.kt
rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/DisplayErrorMessages.kt
index 14bb6471f..ac29855cd 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/DisplayErrorMessages.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/DisplayErrorMessages.kt
@@ -18,20 +18,24 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.amethyst.ui.components
+package com.vitorpamplona.amethyst.ui.components.toasts
import androidx.compose.runtime.Composable
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
+import com.vitorpamplona.amethyst.ui.components.toasts.multiline.MultiErrorToastMsg
+import com.vitorpamplona.amethyst.ui.components.toasts.multiline.MultiUserErrorMessageDialog
+import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
-import com.vitorpamplona.amethyst.ui.screen.loggedIn.ResourceToastMsg
-import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg
-import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThrowableToastMsg
import com.vitorpamplona.amethyst.ui.stringRes
@Composable
-fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
- val openDialogMsg = accountViewModel.toasts.collectAsStateWithLifecycle(null)
+fun DisplayErrorMessages(
+ toastManager: ToastManager,
+ accountViewModel: AccountViewModel,
+ nav: INav,
+) {
+ val openDialogMsg = toastManager.toasts.collectAsStateWithLifecycle(null)
openDialogMsg.value?.let { obj ->
when (obj) {
@@ -41,14 +45,14 @@ fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
stringRes(obj.titleResId),
stringRes(obj.resourceId, *obj.params),
) {
- accountViewModel.clearToasts()
+ toastManager.clearToasts()
}
} else {
InformationDialog(
stringRes(obj.titleResId),
stringRes(obj.resourceId),
) {
- accountViewModel.clearToasts()
+ toastManager.clearToasts()
}
}
@@ -57,7 +61,7 @@ fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
obj.title,
obj.msg,
) {
- accountViewModel.clearToasts()
+ toastManager.clearToasts()
}
is ThrowableToastMsg ->
@@ -66,8 +70,10 @@ fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
obj.msg,
obj.throwable,
) {
- accountViewModel.clearToasts()
+ toastManager.clearToasts()
}
+
+ is MultiErrorToastMsg -> MultiUserErrorMessageDialog(obj, accountViewModel, nav)
}
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ResourceToastMsg.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ResourceToastMsg.kt
new file mode 100644
index 000000000..1ca182da0
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ResourceToastMsg.kt
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.ui.components.toasts
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+class ResourceToastMsg(
+ val titleResId: Int,
+ val resourceId: Int,
+ val params: Array? = null,
+) : ToastMsg()
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/StringToastMsg.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/StringToastMsg.kt
new file mode 100644
index 000000000..1cd090262
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/StringToastMsg.kt
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.ui.components.toasts
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+class StringToastMsg(
+ val title: String,
+ val msg: String,
+) : ToastMsg()
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ThrowableToastMsg.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ThrowableToastMsg.kt
new file mode 100644
index 000000000..1ee9f555c
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ThrowableToastMsg.kt
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.ui.components.toasts
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+class ThrowableToastMsg(
+ val titleResId: Int,
+ val msg: String? = null,
+ val throwable: Throwable,
+) : ToastMsg()
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ToastManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ToastManager.kt
new file mode 100644
index 000000000..95e1830e1
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ToastManager.kt
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.ui.components.toasts
+
+import com.vitorpamplona.amethyst.model.User
+import com.vitorpamplona.amethyst.ui.components.toasts.multiline.MultiErrorToastMsg
+import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class ToastManager {
+ val toasts = MutableStateFlow(null)
+
+ fun clearToasts() {
+ toasts.tryEmit(null)
+ }
+
+ fun toast(
+ title: String,
+ message: String,
+ ) {
+ toasts.tryEmit(StringToastMsg(title, message))
+ }
+
+ fun toast(
+ titleResId: Int,
+ resourceId: Int,
+ ) {
+ toasts.tryEmit(ResourceToastMsg(titleResId, resourceId))
+ }
+
+ fun toast(
+ titleResId: Int,
+ message: String?,
+ throwable: Throwable,
+ ) {
+ toasts.tryEmit(ThrowableToastMsg(titleResId, message, throwable))
+ }
+
+ fun toast(
+ titleResId: Int,
+ resourceId: Int,
+ vararg params: String,
+ ) {
+ toasts.tryEmit(ResourceToastMsg(titleResId, resourceId, params))
+ }
+
+ fun toast(
+ titleResId: Int,
+ message: String,
+ user: User?,
+ ) {
+ val current = toasts.value
+ if (current is MultiErrorToastMsg && current.titleResId == titleResId) {
+ current.add(message, user)
+ } else {
+ toasts.tryEmit(MultiErrorToastMsg(titleResId).also { it.add(message, user) })
+ }
+ }
+
+ fun toast(
+ titleResId: Int,
+ data: UserBasedErrorMessage,
+ ) {
+ val current = toasts.value
+ if (current is MultiErrorToastMsg && current.titleResId == titleResId) {
+ current.add(data)
+ } else {
+ toasts.tryEmit(MultiErrorToastMsg(titleResId).also { it.add(data) })
+ }
+ }
+}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ToastMsg.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ToastMsg.kt
new file mode 100644
index 000000000..285ae8af6
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/ToastMsg.kt
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.ui.components.toasts
+
+import androidx.compose.runtime.Immutable
+
+@Immutable
+open class ToastMsg
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiUserMessageDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/ErrorList.kt
similarity index 65%
rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiUserMessageDialog.kt
rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/ErrorList.kt
index 789f052bc..a822bde13 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiUserMessageDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/ErrorList.kt
@@ -18,11 +18,10 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-package com.vitorpamplona.amethyst.ui.note
+package com.vitorpamplona.amethyst.ui.components.toasts.multiline
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -31,56 +30,44 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.selection.SelectionContainer
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Done
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
-import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
+import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
-import com.vitorpamplona.amethyst.ui.theme.Size16dp
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
import com.vitorpamplona.amethyst.ui.theme.Size30dp
import com.vitorpamplona.amethyst.ui.theme.Size40dp
import com.vitorpamplona.amethyst.ui.theme.Size5dp
-import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.update
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@Composable
@Preview
-fun MultiUserErrorMessageContentPreview() {
+fun ErrorListPreview() {
val accountViewModel = mockAccountViewModel()
- val nav = EmptyNav
var user1: User? = null
var user2: User? = null
@@ -88,107 +75,43 @@ fun MultiUserErrorMessageContentPreview() {
runBlocking {
withContext(Dispatchers.IO) {
- user1 = LocalCache.getOrCreateUser("aaabccaabbccaabbcc")
- user2 = LocalCache.getOrCreateUser("bbbccabbbccabbbcca")
- user3 = LocalCache.getOrCreateUser("ccaadaccaadaccaada")
+ user1 = LocalCache.getOrCreateUser("aaabccaabbccaabbccabdd")
+ user2 = LocalCache.getOrCreateUser("bbbccabbbccabbbccaabdd")
+ user3 = LocalCache.getOrCreateUser("ccaadaccaadaccaadaabdd")
}
}
- val model: UserBasedErrorMessageViewModel = viewModel()
+ val model = MultiErrorToastMsg(R.string.error_dialog_zap_error)
model.add("Could not fetch invoice from https://minibits.cash/.well-known/lnurlp/victorieeman: There are too many unpaid invoices for this name.", user1)
model.add("No Wallets found to pay a lightning invoice. Please install a Lightning wallet to use zaps.", user2)
model.add("Could not fetch invoice", user3)
ThemeComparisonColumn {
- MultiUserErrorMessageDialogInner(
- title = "Couldn't not zap",
+ ErrorList(
model = model,
accountViewModel = accountViewModel,
- nav = nav,
+ nav = EmptyNav,
)
}
}
-@Stable
-class UserBasedErrorMessageViewModel : ViewModel() {
- val errors = MutableStateFlow>(emptyList())
- val hasErrors = errors.map { it.isNotEmpty() }
-
- fun add(
- message: String,
- user: User?,
- ) {
- add(UserBasedErrorMessage(message, user))
- }
-
- fun add(newError: UserBasedErrorMessage) {
- errors.update {
- it + newError
- }
- }
-
- fun clearErrors() {
- errors.update {
- emptyList()
- }
- }
-}
-
-class UserBasedErrorMessage(
- val error: String,
- val user: User?,
-)
-
@Composable
-fun MultiUserErrorMessageDialog(
- title: String,
- model: UserBasedErrorMessageViewModel,
+fun ErrorList(
+ model: MultiErrorToastMsg,
accountViewModel: AccountViewModel,
nav: INav,
) {
- val hasErrors by model.hasErrors.collectAsStateWithLifecycle(false)
- if (hasErrors) {
- MultiUserErrorMessageDialogInner(title, model, accountViewModel, nav)
+ val errorState by model.errors.collectAsStateWithLifecycle()
+ LazyColumn {
+ itemsIndexed(errorState) { index, it ->
+ ErrorRow(it, accountViewModel, nav)
+ if (index < errorState.size - 1) {
+ HorizontalDivider(thickness = DividerThickness)
+ }
+ }
}
}
-@Composable
-fun MultiUserErrorMessageDialogInner(
- title: String,
- model: UserBasedErrorMessageViewModel,
- accountViewModel: AccountViewModel,
- nav: INav,
-) {
- AlertDialog(
- onDismissRequest = model::clearErrors,
- title = { Text(title) },
- text = {
- val errorState by model.errors.collectAsStateWithLifecycle(emptyList())
- LazyColumn {
- itemsIndexed(errorState) { index, it ->
- ErrorRow(it, accountViewModel, nav)
- if (index < errorState.size - 1) {
- HorizontalDivider(thickness = DividerThickness)
- }
- }
- }
- },
- confirmButton = {
- Button(
- onClick = model::clearErrors,
- contentPadding = PaddingValues(horizontal = Size16dp),
- ) {
- Icon(
- imageVector = Icons.Outlined.Done,
- contentDescription = null,
- )
- Spacer(StdHorzSpacer)
- Text(stringRes(R.string.error_dialog_button_ok))
- }
- },
- )
-}
-
@Composable
fun ErrorRow(
errorState: UserBasedErrorMessage,
@@ -201,7 +124,7 @@ fun ErrorRow(
) {
errorState.user?.let {
Column(Modifier.width(Size40dp), horizontalAlignment = Alignment.Start) {
- UserPicture(errorState.user, Size30dp, Modifier, accountViewModel, nav)
+ UserPicture(it, Size30dp, Modifier, accountViewModel, nav)
Spacer(StdVertSpacer)
IconButton(
modifier = Size30Modifier,
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/MultiErrorToastMsg.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/MultiErrorToastMsg.kt
new file mode 100644
index 000000000..ff42feb67
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/MultiErrorToastMsg.kt
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.ui.components.toasts.multiline
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.amethyst.model.User
+import com.vitorpamplona.amethyst.ui.components.toasts.ToastMsg
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
+
+@Immutable
+class MultiErrorToastMsg(
+ val titleResId: Int,
+) : ToastMsg() {
+ val errors = MutableStateFlow>(emptyList())
+
+ fun add(
+ message: String,
+ user: User?,
+ ) {
+ add(UserBasedErrorMessage(message, user))
+ }
+
+ fun add(newError: UserBasedErrorMessage) {
+ errors.update {
+ it + newError
+ }
+ }
+}
+
+class UserBasedErrorMessage(
+ val error: String,
+ val user: User?,
+)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/MultiUserErrorMessageDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/MultiUserErrorMessageDialog.kt
new file mode 100644
index 000000000..edf0dea2b
--- /dev/null
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/toasts/multiline/MultiUserErrorMessageDialog.kt
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.amethyst.ui.components.toasts.multiline
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Done
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.tooling.preview.Preview
+import com.vitorpamplona.amethyst.R
+import com.vitorpamplona.amethyst.model.LocalCache
+import com.vitorpamplona.amethyst.model.User
+import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
+import com.vitorpamplona.amethyst.ui.navigation.INav
+import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
+import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
+import com.vitorpamplona.amethyst.ui.stringRes
+import com.vitorpamplona.amethyst.ui.theme.Size16dp
+import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
+import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+
+@Composable
+@Preview
+fun MultiUserErrorMessageContentPreview() {
+ val accountViewModel = mockAccountViewModel()
+ val nav = EmptyNav
+
+ var user1: User? = null
+ var user2: User? = null
+ var user3: User? = null
+
+ runBlocking {
+ withContext(Dispatchers.IO) {
+ user1 = LocalCache.getOrCreateUser("aaabccaabbccaabbccabdd")
+ user2 = LocalCache.getOrCreateUser("bbbccabbbccabbbccaabdd")
+ user3 = LocalCache.getOrCreateUser("ccaadaccaadaccaadaabdd")
+ }
+ }
+
+ val model = MultiErrorToastMsg(R.string.error_dialog_zap_error)
+ model.add("Could not fetch invoice from https://minibits.cash/.well-known/lnurlp/victorieeman: There are too many unpaid invoices for this name.", user1)
+ model.add("No Wallets found to pay a lightning invoice. Please install a Lightning wallet to use zaps.", user2)
+ model.add("Could not fetch invoice", user3)
+
+ ThemeComparisonColumn {
+ MultiUserErrorMessageDialog(
+ model = model,
+ accountViewModel = accountViewModel,
+ nav = nav,
+ )
+ }
+}
+
+@Composable
+fun MultiUserErrorMessageDialog(
+ model: MultiErrorToastMsg,
+ accountViewModel: AccountViewModel,
+ nav: INav,
+) {
+ AlertDialog(
+ onDismissRequest = accountViewModel.toastManager::clearToasts,
+ title = { Text(stringRes(model.titleResId)) },
+ text = {
+ ErrorList(model, accountViewModel, nav)
+ },
+ confirmButton = {
+ Button(
+ onClick = accountViewModel.toastManager::clearToasts,
+ contentPadding = PaddingValues(horizontal = Size16dp),
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Done,
+ contentDescription = null,
+ )
+ Spacer(StdHorzSpacer)
+ Text(stringRes(R.string.error_dialog_button_ok))
+ }
+ },
+ )
+}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt
index da10b3858..6fc4cf22c 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt
@@ -31,7 +31,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverNIP89FeedFilter(
val account: Account,
) : AdditiveFeedFilter() {
- val lastAnnounced = 90 * 24 * 60 * 60 // 90 Days ago
+ val lastAnnounced = 365 * 24 * 60 * 60 // 365 Days ago
// TODO better than announced would be last active, as this requires the DVM provider to regularly update the NIP89 announcement
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList()
@@ -43,8 +43,6 @@ open class DiscoverNIP89FeedFilter(
followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
override fun feed(): List {
- val filterParams = buildFilterParams(account)
-
val notes =
LocalCache.addressables.filterIntoSet { _, it ->
acceptDVM(it)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt
index 45c7228a0..b8c6abcb6 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt
@@ -51,8 +51,8 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.MainActivity
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataScreen
import com.vitorpamplona.amethyst.ui.actions.relays.AllRelayListView
-import com.vitorpamplona.amethyst.ui.components.DisplayErrorMessages
import com.vitorpamplona.amethyst.ui.components.DisplayNotifyMessages
+import com.vitorpamplona.amethyst.ui.components.toasts.DisplayErrorMessages
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
@@ -363,7 +363,7 @@ fun AppNavigation(
NavigateIfIntentRequested(nav, accountViewModel, accountStateViewModel)
- DisplayErrorMessages(accountViewModel)
+ DisplayErrorMessages(accountViewModel.toastManager, accountViewModel, nav)
DisplayNotifyMessages(accountViewModel, nav)
}
@@ -446,7 +446,7 @@ private fun NavigateIfIntentRequested(
actionableNextPage = null
} else {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.invalid_nip19_uri,
R.string.invalid_nip19_uri_description,
intentNextPage,
@@ -501,7 +501,7 @@ private fun NavigateIfIntentRequested(
} else {
scope.launch {
delay(1000)
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.invalid_nip19_uri,
R.string.invalid_nip19_uri_description,
uri,
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt
index a1ef2b030..03769d48a 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt
@@ -559,7 +559,7 @@ fun ListContent(
accountViewModel.setTorSettings(torSettings)
},
onError = {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.could_not_connect_to_tor),
it,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
index 1f0a9252e..685f72bff 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt
@@ -64,7 +64,6 @@ import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
-import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.NoteState
@@ -251,21 +250,23 @@ fun DisplaySecretEmojiAsReaction(
accountViewModel: AccountViewModel,
nav: INav,
) {
- var secretContent by remember {
- mutableStateOf(null)
+ var secretContent by remember(reaction) {
+ mutableStateOf(CachedRichTextParser.cachedText(EmojiCoder.decode(reaction), EmptyTagList))
}
var showPopup by remember {
mutableStateOf(false)
}
- LaunchedEffect(reaction) {
- launch(Dispatchers.Default) {
- secretContent =
- CachedRichTextParser.parseText(
- EmojiCoder.decode(reaction),
- EmptyTagList,
- )
+ if (secretContent == null) {
+ LaunchedEffect(reaction) {
+ launch(Dispatchers.Default) {
+ secretContent =
+ CachedRichTextParser.parseText(
+ EmojiCoder.decode(reaction),
+ EmptyTagList,
+ )
+ }
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt
index 6d8bb6f52..725f74977 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt
@@ -76,11 +76,11 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
+import com.vitorpamplona.amethyst.ui.components.toasts.StringToastMsg
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
-import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockVitorAccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
@@ -530,23 +530,23 @@ fun ZapVote(
indication = ripple24dp,
onClick = {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_send_zaps,
)
} else if (pollViewModel.isPollClosed()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.poll_unable_to_vote,
R.string.poll_is_closed_explainer,
)
} else if (isLoggedUser) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.poll_unable_to_vote,
R.string.poll_author_no_vote,
)
} else if (pollViewModel.isVoteAmountAtomic() && poolOption.zappedByLoggedIn.value) {
// only allow one vote per option when min==max, i.e. atomic vote amount specified
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.poll_unable_to_vote,
R.string.one_vote_per_user_on_atomic_votes,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
index 9a9e06a76..3ee5b3c7d 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt
@@ -99,12 +99,12 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
import com.vitorpamplona.amethyst.model.FeatureSetType
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
+import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource.user
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius
@@ -662,7 +662,7 @@ fun ReplyReaction(
modifier = iconSizeModifier,
onClick = {
if (baseNote.isDraft()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.draft_note,
R.string.it_s_not_possible_to_reply_to_a_draft_note,
)
@@ -670,7 +670,7 @@ fun ReplyReaction(
if (accountViewModel.isWriteable()) {
onPress()
} else {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_reply,
)
@@ -986,7 +986,7 @@ private fun likeClick(
onWantsToSignReaction: () -> Unit,
) {
if (baseNote.isDraft()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.draft_note,
R.string.it_s_not_possible_to_react_to_a_draft_note,
)
@@ -995,12 +995,12 @@ private fun likeClick(
val choices = accountViewModel.reactionChoices()
if (choices.isEmpty()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.no_reactions_setup,
R.string.no_reaction_type_setup_long_press_to_change,
)
} else if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_like_posts,
)
@@ -1025,7 +1025,6 @@ fun ZapReaction(
var wantsToZap by remember { mutableStateOf(false) }
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
var wantsToSetCustomZap by remember { mutableStateOf(false) }
- val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
var wantsToPay by
remember(baseNote) {
mutableStateOf>(
@@ -1060,7 +1059,7 @@ fun ZapReaction(
onError = { _, message, user ->
scope.launch {
zappingProgress = 0f
- errorViewModel.add(message, user)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
}
},
onPayViaIntent = { wantsToPay = it },
@@ -1089,7 +1088,7 @@ fun ZapReaction(
onError = { _, message, user ->
scope.launch {
zappingProgress = 0f
- errorViewModel.add(message, user)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
}
},
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
@@ -1097,13 +1096,6 @@ fun ZapReaction(
)
}
- MultiUserErrorMessageDialog(
- title = stringRes(id = R.string.error_dialog_zap_error),
- model = errorViewModel,
- accountViewModel = accountViewModel,
- nav = nav,
- )
-
if (wantsToChangeZapAmount) {
UpdateZapAmountDialog(
onClose = { wantsToChangeZapAmount = false },
@@ -1120,12 +1112,12 @@ fun ZapReaction(
wantsToPay = persistentListOf()
scope.launch {
zappingProgress = 0f
- errorViewModel.add(it)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
justShowError = {
scope.launch {
- errorViewModel.add(it)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
)
@@ -1137,7 +1129,7 @@ fun ZapReaction(
onError = { _, message, user ->
scope.launch {
zappingProgress = 0f
- errorViewModel.add(message, user)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
}
},
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
@@ -1191,7 +1183,7 @@ fun zapClick(
onPayViaIntent: (ImmutableList) -> Unit,
) {
if (baseNote.isDraft()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.draft_note,
R.string.it_s_not_possible_to_zap_to_a_draft_note,
)
@@ -1201,12 +1193,12 @@ fun zapClick(
val choices = accountViewModel.zapAmountChoices()
if (choices.isEmpty()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.error_dialog_zap_error,
R.string.no_zap_amount_setup_long_press_to_change,
)
} else if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.error_dialog_zap_error,
R.string.login_with_a_private_key_to_be_able_to_send_zaps,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt
index 57209e901..d96d742a1 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt
@@ -169,7 +169,7 @@ fun RenderRelay(
openRelayDialog = true
},
onError = { url, errorCode, exceptionMessage ->
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.unable_to_download_relay_document,
when (errorCode) {
Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL ->
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ShowEmojiSuggestionList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ShowEmojiSuggestionList.kt
index fa5a6bfb5..c2ae42a05 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ShowEmojiSuggestionList.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ShowEmojiSuggestionList.kt
@@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.UrlImageVie
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size10dp
+import com.vitorpamplona.amethyst.ui.theme.Size40Modifier
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent
import kotlinx.collections.immutable.toImmutableList
@@ -108,11 +109,11 @@ fun ShowEmojiSuggestionList(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = spacedBy(Size10dp),
) {
- Box(Modifier.size(40.dp)) {
+ Box(Size40Modifier) {
UrlImageView(it.url, accountViewModel)
}
Text(it.code, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
- Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) {
+ Box(Size40Modifier, contentAlignment = Alignment.Center) {
IconButton(
onClick = {
onFullSize(it)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt
index d1e2162b0..93cbd2be3 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt
@@ -53,7 +53,6 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -79,13 +78,10 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
-import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
-import com.vitorpamplona.amethyst.service.CachedRichTextParser
import com.vitorpamplona.amethyst.service.firstFullChar
import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius
-import com.vitorpamplona.amethyst.ui.components.CoreSecretMessage
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
import com.vitorpamplona.amethyst.ui.navigation.INav
@@ -380,53 +376,6 @@ private fun RenderReactionOption(
}
}
-@Composable
-fun DisplaySecretEmoji(
- text: String,
- state: RichTextViewerState,
- callbackUri: String?,
- canPreview: Boolean,
- quotesLeft: Int,
- backgroundColor: MutableState,
- accountViewModel: AccountViewModel,
- nav: INav,
-) {
- if (canPreview && quotesLeft > 0) {
- var secretContent by remember {
- mutableStateOf(null)
- }
-
- var showPopup by remember {
- mutableStateOf(false)
- }
-
- LaunchedEffect(text) {
- launch(Dispatchers.Default) {
- secretContent =
- CachedRichTextParser.parseText(
- EmojiCoder.decode(text),
- state.tags,
- )
- }
- }
-
- val localSecretContent = secretContent
-
- AnimatedBorderTextCornerRadius(
- text,
- Modifier.clickable {
- showPopup = !showPopup
- },
- )
-
- if (localSecretContent != null && showPopup) {
- CoreSecretMessage(localSecretContent, callbackUri, quotesLeft, backgroundColor, accountViewModel, nav)
- }
- } else {
- Text(text)
- }
-}
-
@Composable
private fun EmojiSelector(
accountViewModel: AccountViewModel,
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt
index bc1d43280..6735a220e 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt
@@ -337,12 +337,12 @@ fun UpdateZapAmountContent(
postViewModel.updateNIP47(nip47uri)
} catch (e: IllegalArgumentException) {
if (e.message != null) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.error_parsing_nip47_title),
stringRes(context, R.string.error_parsing_nip47, nip47uri, e.message!!),
)
} else {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.error_parsing_nip47_title),
stringRes(context, R.string.error_parsing_nip47_no_error, nip47uri),
)
@@ -510,12 +510,12 @@ fun UpdateZapAmountContent(
postViewModel.updateNIP47(it)
} catch (e: IllegalArgumentException) {
if (e.message != null) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.error_parsing_nip47_title),
stringRes(context, R.string.error_parsing_nip47, it, e.message!!),
)
} else {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.error_parsing_nip47_title),
stringRes(context, R.string.error_parsing_nip47_no_error, it),
)
@@ -592,7 +592,7 @@ fun UpdateZapAmountContent(
onValueChange = { postViewModel.walletConnectSecret = it },
keyboardOptions =
KeyboardOptions(
- autoCorrect = false,
+ autoCorrectEnabled = false,
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Go,
),
@@ -611,7 +611,7 @@ fun UpdateZapAmountContent(
context = context,
keyguardLauncher = keyguardLauncher,
onApproved = { showPassword = true },
- onError = { title, message -> accountViewModel.toast(title, message) },
+ onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
} else {
showPassword = false
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt
index b9c0da284..364865fb5 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt
@@ -66,6 +66,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
+import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt
index 73efdf414..1a9f38ec0 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt
@@ -201,7 +201,7 @@ fun ShowFollowingOrUnfollowingButton(
if (isFollowing) {
UnfollowButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_unfollow,
)
@@ -212,7 +212,7 @@ fun ShowFollowingOrUnfollowingButton(
} else {
FollowButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_follow,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt
index 018725713..573fe81ca 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayOts.kt
@@ -75,7 +75,7 @@ fun DisplayOts(
val fullDateTime =
SimpleDateFormat.getDateTimeInstance().format(Date(unixtimestamp * 1000))
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.ots_info_title,
R.string.ots_info_description,
fullDateTime,
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt
index c92fd13aa..473915ffc 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/ZapTheDevsCard.kt
@@ -58,7 +58,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
@@ -72,10 +71,8 @@ import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.note.CloseIcon
-import com.vitorpamplona.amethyst.ui.note.MultiUserErrorMessageDialog
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
-import com.vitorpamplona.amethyst.ui.note.UserBasedErrorMessageViewModel
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
import com.vitorpamplona.amethyst.ui.note.ZapIcon
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
@@ -293,7 +290,6 @@ fun ZapDonationButton(
nav: INav,
) {
var wantsToZap by remember { mutableStateOf?>(null) }
- val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
var wantsToPay by
remember(baseNote) {
mutableStateOf>(
@@ -320,7 +316,7 @@ fun ZapDonationButton(
onError = { _, message, toUser ->
scope.launch {
zappingProgress = 0f
- errorViewModel.add(message, toUser)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, toUser)
}
},
onPayViaIntent = { wantsToPay = it },
@@ -344,7 +340,7 @@ fun ZapDonationButton(
onError = { _, message, user ->
scope.launch {
zappingProgress = 0f
- errorViewModel.add(message, user)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
}
},
onProgress = {
@@ -354,13 +350,6 @@ fun ZapDonationButton(
)
}
- MultiUserErrorMessageDialog(
- title = stringRes(id = R.string.error_dialog_zap_error),
- model = errorViewModel,
- accountViewModel = accountViewModel,
- nav = nav,
- )
-
if (wantsToPay.isNotEmpty()) {
PayViaIntentDialog(
payingInvoices = wantsToPay,
@@ -370,12 +359,12 @@ fun ZapDonationButton(
wantsToPay = persistentListOf()
scope.launch {
zappingProgress = 0f
- errorViewModel.add(it)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
justShowError = {
scope.launch {
- errorViewModel.add(it)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
)
@@ -441,7 +430,7 @@ fun customZapClick(
onPayViaIntent: (ImmutableList) -> Unit,
) {
if (baseNote.isDraft()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.draft_note,
R.string.it_s_not_possible_to_zap_to_a_draft_note,
)
@@ -451,12 +440,12 @@ fun customZapClick(
val choices = accountViewModel.zapAmountChoices()
if (choices.isEmpty()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.error_dialog_zap_error),
stringRes(context, R.string.no_zap_amount_setup_long_press_to_change),
)
} else if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
stringRes(context, R.string.error_dialog_zap_error),
stringRes(context, R.string.login_with_a_private_key_to_be_able_to_send_zaps),
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt
index 4c3aaa019..3f400a768 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Report.kt
@@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
+import com.vitorpamplona.quartz.nip56Reports.ReportType
@Composable
fun RenderReport(
@@ -52,16 +53,18 @@ fun RenderReport(
val reportType =
base
.map {
- when (it.reportType) {
- ReportEvent.ReportType.EXPLICIT -> stringRes(R.string.explicit_content)
- ReportEvent.ReportType.NUDITY -> stringRes(R.string.nudity)
- ReportEvent.ReportType.PROFANITY -> stringRes(R.string.profanity_hateful_speech)
- ReportEvent.ReportType.SPAM -> stringRes(R.string.spam)
- ReportEvent.ReportType.IMPERSONATION -> stringRes(R.string.impersonation)
- ReportEvent.ReportType.ILLEGAL -> stringRes(R.string.illegal_behavior)
- ReportEvent.ReportType.MALWARE -> stringRes(R.string.malware)
- ReportEvent.ReportType.MOD -> stringRes(R.string.mod)
- ReportEvent.ReportType.OTHER -> stringRes(R.string.other)
+ when (it.type) {
+ ReportType.EXPLICIT -> stringRes(R.string.explicit_content)
+ ReportType.NUDITY -> stringRes(R.string.nudity)
+ ReportType.PROFANITY -> stringRes(R.string.profanity_hateful_speech)
+ ReportType.SPAM -> stringRes(R.string.spam)
+ ReportType.IMPERSONATION -> stringRes(R.string.impersonation)
+ ReportType.ILLEGAL -> stringRes(R.string.illegal_behavior)
+ ReportType.MALWARE -> stringRes(R.string.malware)
+ ReportType.MOD -> stringRes(R.string.mod)
+ ReportType.OTHER -> stringRes(R.string.other)
+ ReportType.HARASSMENT -> stringRes(R.string.harassment)
+ null -> stringRes(R.string.other)
}
}.toSet()
.joinToString(", ")
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt
index 2fbcefca7..3e5675030 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt
@@ -221,7 +221,7 @@ fun DisplayFileList(
ContextCompat.startActivity(context, intent, null)
} catch (e: Exception) {
if (e is CancellationException) throw e
- accountViewModel.toast(R.string.torrent_failure, R.string.torrent_no_apps)
+ accountViewModel.toastManager.toast(R.string.torrent_failure, R.string.torrent_no_apps)
}
}, Modifier.size(Size30dp)) {
DownloadForOfflineIcon(Size20dp, MaterialTheme.colorScheme.onBackground)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt
index d19575501..473796764 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt
@@ -156,7 +156,7 @@ private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) {
contract = ActivityResultContracts.StartActivityForResult(),
onResult = { result ->
if (result.resultCode != Activity.RESULT_OK) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.sign_request_rejected,
R.string.sign_request_rejected_description,
)
@@ -183,7 +183,7 @@ private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) {
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e("Signer", "Error opening Signer app", e)
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.error_opening_external_signer,
R.string.error_opening_external_signer_description,
)
@@ -203,7 +203,7 @@ private fun ListenToExternalSignerIfNeeded(accountViewModel: AccountViewModel) {
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e("Signer", "Error opening Signer app", e)
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.error_opening_external_signer,
R.string.error_opening_external_signer_description,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt
index 185e75900..e92b95028 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountBackupDialog.kt
@@ -321,7 +321,7 @@ private fun NSecCopyButton(accountViewModel: AccountViewModel) {
context = context,
keyguardLauncher = keyguardLauncher,
onApproved = { copyNSec(context, scope, accountViewModel.account, clipboardManager) },
- onError = { title, message -> accountViewModel.toast(title, message) },
+ onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
},
shape = ButtonBorder,
@@ -371,7 +371,7 @@ private fun EncryptNSecCopyButton(
context = context,
keyguardLauncher = keyguardLauncher,
onApproved = { encryptCopyNSec(password, context, scope, accountViewModel, clipboardManager) },
- onError = { title, message -> accountViewModel.toast(title, message) },
+ onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
},
shape = ButtonBorder,
@@ -496,7 +496,7 @@ private fun QrCodeButtonBase(
context = context,
keyguardLauncher = keyguardLauncher,
onApproved = { dialogOpen = true },
- onError = { title, message -> accountViewModel.toast(title, message) },
+ onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
},
) {
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt
index 6b131680e..349caffc0 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt
@@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.ui.actions.Dao
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
+import com.vitorpamplona.amethyst.ui.components.toasts.ToastManager
import com.vitorpamplona.amethyst.ui.feeds.FeedState
import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
@@ -102,7 +103,7 @@ import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip47WalletConnect.Response
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent
-import com.vitorpamplona.quartz.nip56Reports.ReportEvent
+import com.vitorpamplona.quartz.nip56Reports.ReportType
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
@@ -123,8 +124,6 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -137,25 +136,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-@Immutable open class ToastMsg
-
-@Immutable class StringToastMsg(
- val title: String,
- val msg: String,
-) : ToastMsg()
-
-@Immutable class ResourceToastMsg(
- val titleResId: Int,
- val resourceId: Int,
- val params: Array? = null,
-) : ToastMsg()
-
-@Immutable class ThrowableToastMsg(
- val titleResId: Int,
- val msg: String? = null,
- val throwable: Throwable,
-) : ToastMsg()
-
@Stable
class AccountViewModel(
accountSettings: AccountSettings,
@@ -189,7 +169,7 @@ class AccountViewModel(
val dmRelays: StateFlow = observeByAuthor(ChatMessageRelayListEvent.KIND, account.signer.pubKey)
val searchRelays: StateFlow = observeByAuthor(SearchRelayListEvent.KIND, account.signer.pubKey)
- val toasts = MutableSharedFlow(0, 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ val toastManager = ToastManager()
val feedStates = AccountFeedContentStates(this)
@@ -269,40 +249,6 @@ class AccountViewModel(
Route.Notification to notificationHasNewItemsFlow,
)
- fun clearToasts() {
- viewModelScope.launch { toasts.emit(null) }
- }
-
- fun toast(
- title: String,
- message: String,
- ) {
- viewModelScope.launch { toasts.emit(StringToastMsg(title, message)) }
- }
-
- fun toast(
- titleResId: Int,
- resourceId: Int,
- ) {
- viewModelScope.launch { toasts.emit(ResourceToastMsg(titleResId, resourceId)) }
- }
-
- fun toast(
- titleResId: Int,
- message: String?,
- throwable: Throwable,
- ) {
- viewModelScope.launch { toasts.emit(ThrowableToastMsg(titleResId, message, throwable)) }
- }
-
- fun toast(
- titleResId: Int,
- resourceId: Int,
- vararg params: String,
- ) {
- viewModelScope.launch { toasts.emit(ResourceToastMsg(titleResId, resourceId, params)) }
- }
-
fun isWriteable(): Boolean = account.isWriteable()
fun userProfile(): User = account.userProfile()
@@ -741,7 +687,7 @@ class AccountViewModel(
fun report(
note: Note,
- type: ReportEvent.ReportType,
+ type: ReportType,
content: String = "",
) {
viewModelScope.launch(Dispatchers.IO) { account.report(note, type, content) }
@@ -749,7 +695,7 @@ class AccountViewModel(
fun report(
user: User,
- type: ReportEvent.ReportType,
+ type: ReportType,
) {
viewModelScope.launch(Dispatchers.IO) {
account.report(user, type)
@@ -1406,7 +1352,7 @@ class AccountViewModel(
onMore: () -> Unit,
) {
if (baseNote.isDraft()) {
- toast(
+ toastManager.toast(
R.string.draft_note,
R.string.it_s_not_possible_to_quote_to_a_draft_note,
)
@@ -1420,7 +1366,7 @@ class AccountViewModel(
onMore()
}
} else {
- toast(
+ toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_boost_posts,
)
@@ -1766,6 +1712,7 @@ fun mockAccountViewModel(): AccountViewModel {
KeyPair(
privKey = Hex.decode("0f761f8a5a481e26f06605a1d9b3e9eba7a107d351f43c43a57469b788274499"),
pubKey = Hex.decode("989c3734c46abac7ce3ce229971581a5a6ee39cdd6aa7261a55823fa7f8c4799"),
+ forceReplacePubkey = false,
),
),
sharedPreferencesViewModel.sharedPrefs,
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt
index 07f698f4c..8a27ae796 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt
@@ -527,7 +527,7 @@ fun NewPostScreen(
it,
accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality ->
- postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, false, server, accountViewModel::toast, context)
+ postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, false, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server)
}
@@ -558,7 +558,7 @@ fun NewPostScreen(
postViewModel.wantsInvoice = false
},
onClose = { postViewModel.wantsInvoice = false },
- onError = { title, message -> accountViewModel.toast(title, message) },
+ onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt
index 1fb25ddfa..3ae9a888c 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ReportNoteDialog.kt
@@ -63,7 +63,7 @@ import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.WarningColor
-import com.vitorpamplona.quartz.nip56Reports.ReportEvent
+import com.vitorpamplona.quartz.nip56Reports.ReportType
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@@ -75,13 +75,13 @@ fun ReportNoteDialog(
) {
val reportTypes =
listOf(
- Pair(ReportEvent.ReportType.SPAM, stringRes(R.string.report_dialog_spam)),
- Pair(ReportEvent.ReportType.PROFANITY, stringRes(R.string.report_dialog_profanity)),
- Pair(ReportEvent.ReportType.IMPERSONATION, stringRes(R.string.report_dialog_impersonation)),
- Pair(ReportEvent.ReportType.NUDITY, stringRes(R.string.report_dialog_nudity)),
- Pair(ReportEvent.ReportType.ILLEGAL, stringRes(R.string.report_dialog_illegal)),
- Pair(ReportEvent.ReportType.MALWARE, stringRes(R.string.report_malware)),
- Pair(ReportEvent.ReportType.MOD, stringRes(R.string.report_mod)),
+ Pair(ReportType.SPAM, stringRes(R.string.report_dialog_spam)),
+ Pair(ReportType.PROFANITY, stringRes(R.string.report_dialog_profanity)),
+ Pair(ReportType.IMPERSONATION, stringRes(R.string.report_dialog_impersonation)),
+ Pair(ReportType.NUDITY, stringRes(R.string.report_dialog_nudity)),
+ Pair(ReportType.ILLEGAL, stringRes(R.string.report_dialog_illegal)),
+ Pair(ReportType.MALWARE, stringRes(R.string.report_malware)),
+ Pair(ReportType.MOD, stringRes(R.string.report_mod)),
)
val reasonOptions = remember { reportTypes.map { TitleExplainer(it.second) }.toImmutableList() }
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploadDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploadDialog.kt
index 65ab604ff..c13e6c6a2 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploadDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploadDialog.kt
@@ -65,7 +65,7 @@ fun RoomChatFileUploadDialog(
},
upload = {
channelScreenModel.upload(
- onError = accountViewModel::toast,
+ onError = accountViewModel.toastManager::toast,
context = context,
onceUploaded = onUpload,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelFileUploadDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelFileUploadDialog.kt
index 6b7def77b..b90f6346f 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelFileUploadDialog.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelFileUploadDialog.kt
@@ -126,7 +126,7 @@ fun ChannelFileUploadDialog(
},
upload = {
channelScreenModel.upload(
- onError = accountViewModel::toast,
+ onError = accountViewModel.toastManager::toast,
context = context,
onceUploaded = onUpload,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt
index 0ce4facea..d11c2d114 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListTabs.kt
@@ -47,12 +47,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
+import com.vitorpamplona.amethyst.ui.theme.Size40dp
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.launch
@@ -93,7 +93,7 @@ fun MessagesTabHeader(
IconButton(
modifier =
Modifier
- .size(40.dp)
+ .size(Size40dp)
.align(Alignment.CenterEnd),
onClick = { moreActionsExpanded = true },
) {
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt
index d7dd79387..b6e7321b3 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt
@@ -70,11 +70,9 @@ import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.note.DVMCard
-import com.vitorpamplona.amethyst.ui.note.MultiUserErrorMessageDialog
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
-import com.vitorpamplona.amethyst.ui.note.UserBasedErrorMessageViewModel
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
import com.vitorpamplona.amethyst.ui.note.ZapIcon
@@ -442,7 +440,6 @@ fun ZapDVMButton(
val noteAuthor = baseNote.author ?: return
var wantsToZap by remember { mutableStateOf?>(null) }
- val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
var wantsToPay by
remember(baseNote) {
mutableStateOf>(
@@ -472,7 +469,7 @@ fun ZapDVMButton(
onError = { _, message, toUser ->
scope.launch {
zappingProgress = 0f
- errorViewModel.add(message, toUser)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, toUser)
}
},
onPayViaIntent = { wantsToPay = it },
@@ -496,7 +493,7 @@ fun ZapDVMButton(
onError = { _, message, user ->
scope.launch {
zappingProgress = 0f
- errorViewModel.add(message, user)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
}
},
onProgress = {
@@ -506,13 +503,6 @@ fun ZapDVMButton(
)
}
- MultiUserErrorMessageDialog(
- title = stringRes(id = R.string.error_dialog_zap_error),
- model = errorViewModel,
- accountViewModel,
- nav,
- )
-
if (wantsToPay.isNotEmpty()) {
PayViaIntentDialog(
payingInvoices = wantsToPay,
@@ -522,12 +512,12 @@ fun ZapDVMButton(
wantsToPay = persistentListOf()
scope.launch {
zappingProgress = 0f
- errorViewModel.add(it)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
justShowError = {
scope.launch {
- errorViewModel.add(it)
+ accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt
index c180e0468..0bde5c607 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt
@@ -208,7 +208,7 @@ fun GeoHashActionOptions(
if (isFollowingTag) {
UnfollowButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_unfollow,
)
@@ -219,7 +219,7 @@ fun GeoHashActionOptions(
} else {
FollowButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_follow,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt
index 9779f81a8..7d60fb079 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt
@@ -187,7 +187,7 @@ fun HashtagActionOptions(
if (isFollowingTag) {
UnfollowButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_unfollow,
)
@@ -198,7 +198,7 @@ fun HashtagActionOptions(
} else {
FollowButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_follow,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt
index 71d3b9651..8aee3d211 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryThumb.kt
@@ -56,12 +56,11 @@ import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
import com.vitorpamplona.amethyst.ui.components.DisplayBlurHash
-import com.vitorpamplona.amethyst.ui.components.DisplayUrlWithLoadingSymbol
import com.vitorpamplona.amethyst.ui.components.GetMediaItem
import com.vitorpamplona.amethyst.ui.components.GetVideoController
import com.vitorpamplona.amethyst.ui.components.ImageUrlWithDownloadButton
+import com.vitorpamplona.amethyst.ui.components.LoadingAnimation
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
-import com.vitorpamplona.amethyst.ui.components.WaitAndDisplay
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon
import com.vitorpamplona.amethyst.ui.note.WatchAuthor
@@ -217,8 +216,8 @@ fun UrlImageView(
defaultModifier,
)
} else {
- WaitAndDisplay {
- DisplayUrlWithLoadingSymbol(content)
+ Box(defaultModifier, contentAlignment = Alignment.Center) {
+ LoadingAnimation()
}
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt
index 9fe7176d2..6a474f8f3 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt
@@ -56,7 +56,7 @@ fun DisplayFollowUnfollowButton(
if (isLoggedInFollowingUser) {
UnfollowButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_unfollow,
)
@@ -68,7 +68,7 @@ fun DisplayFollowUnfollowButton(
if (isUserFollowingLoggedIn) {
FollowButton(R.string.follow_back) {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_follow,
)
@@ -79,7 +79,7 @@ fun DisplayFollowUnfollowButton(
} else {
FollowButton(R.string.follow) {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_follow,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayLNAddress.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayLNAddress.kt
index f80a8a02c..e46498b1f 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayLNAddress.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayLNAddress.kt
@@ -130,7 +130,7 @@ fun DisplayLNAddress(
}
},
onClose = { zapExpanded = false },
- onError = { title, message -> accountViewModel.toast(title, message) },
+ onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
)
}
}
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt
index 79c05f7ef..2a09eb206 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt
@@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.ui.note.externalLinkForUser
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
-import com.vitorpamplona.quartz.nip56Reports.ReportEvent
+import com.vitorpamplona.quartz.nip56Reports.ReportType
@Composable
fun UserProfileDropDownMenu(
@@ -108,42 +108,42 @@ fun UserProfileDropDownMenu(
DropdownMenuItem(
text = { Text(stringRes(id = R.string.report_spam_scam)) },
onClick = {
- accountViewModel.report(user, ReportEvent.ReportType.SPAM)
+ accountViewModel.report(user, ReportType.SPAM)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(R.string.report_hateful_speech)) },
onClick = {
- accountViewModel.report(user, ReportEvent.ReportType.PROFANITY)
+ accountViewModel.report(user, ReportType.PROFANITY)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(id = R.string.report_impersonation)) },
onClick = {
- accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION)
+ accountViewModel.report(user, ReportType.IMPERSONATION)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(R.string.report_nudity_porn)) },
onClick = {
- accountViewModel.report(user, ReportEvent.ReportType.NUDITY)
+ accountViewModel.report(user, ReportType.NUDITY)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(id = R.string.report_illegal_behaviour)) },
onClick = {
- accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL)
+ accountViewModel.report(user, ReportType.ILLEGAL)
onDismiss()
},
)
DropdownMenuItem(
text = { Text(stringRes(id = R.string.report_malware)) },
onClick = {
- accountViewModel.report(user, ReportEvent.ReportType.MALWARE)
+ accountViewModel.report(user, ReportType.MALWARE)
onDismiss()
},
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt
index 334672c81..1d1d17749 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt
@@ -331,7 +331,7 @@ fun MutedWordActionOptions(
if (isMutedWord == true) {
ShowWordButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_show_word,
)
@@ -342,7 +342,7 @@ fun MutedWordActionOptions(
} else {
HideWordButton {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_hide_word,
)
@@ -393,7 +393,7 @@ private fun hideIfWritable(
currentWordToAdd: MutableState,
) {
if (!accountViewModel.isWriteable()) {
- accountViewModel.toast(
+ accountViewModel.toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_hide_word,
)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt
index b200a8b8c..cbdc8f383 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt
@@ -99,9 +99,12 @@ import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
+import com.vitorpamplona.amethyst.ui.theme.Size0dp
+import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.Size40dp
+import com.vitorpamplona.amethyst.ui.theme.Size50dp
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.tor.TorSettings
@@ -200,7 +203,7 @@ fun LoginPage(
contentScale = ContentScale.Inside,
)
- Spacer(modifier = Modifier.height(40.dp))
+ Spacer(modifier = Modifier.height(Size40dp))
KeyTextField(
value = key.value,
@@ -334,9 +337,9 @@ fun LoginPage(
}
}
- Spacer(modifier = Modifier.height(10.dp))
+ Spacer(modifier = Modifier.height(Size10dp))
- Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
+ Box(modifier = Modifier.padding(Size40dp, 0.dp, Size40dp, 0.dp)) {
LoginButton(
enabled = acceptedTerms.value,
processingLogin = processingLogin,
@@ -371,7 +374,7 @@ fun LoginPage(
}
if (PackageUtils.isExternalSignerInstalled(context)) {
- Box(modifier = Modifier.padding(40.dp, 20.dp, 40.dp, 0.dp)) {
+ Box(modifier = Modifier.padding(Size40dp, Size20dp, Size40dp, Size0dp)) {
LoginWithAmberButton(
enabled = acceptedTerms.value,
onClick = {
@@ -551,7 +554,7 @@ fun KeyTextField(
onValueChange = { onValueChange(it, false) },
keyboardOptions =
KeyboardOptions(
- autoCorrect = false,
+ autoCorrectEnabled = false,
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Go,
),
@@ -777,11 +780,11 @@ fun LoginWithAmberButton(
enabled = enabled,
onClick = onClick,
shape = RoundedCornerShape(Size35dp),
- modifier = Modifier.height(50.dp),
+ modifier = Modifier.height(Size50dp),
) {
Text(
text = stringRes(R.string.login_with_external_signer),
- modifier = Modifier.padding(horizontal = 40.dp),
+ modifier = Modifier.padding(horizontal = Size40dp),
)
}
}
@@ -796,9 +799,9 @@ fun LoginButton(
enabled = enabled,
onClick = onClick,
shape = RoundedCornerShape(Size35dp),
- modifier = Modifier.height(50.dp),
+ modifier = Modifier.height(Size50dp),
) {
- Row(modifier = Modifier.padding(horizontal = 40.dp)) {
+ Row(modifier = Modifier.padding(horizontal = Size40dp)) {
if (processingLogin) {
LoadingAnimation()
Spacer(modifier = DoubleHorzSpacer)
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt
index ed6ab88c6..2e45260a1 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt
@@ -86,6 +86,7 @@ val TopBarSize = 50.dp - 64.dp
val Size0dp = 0.dp
val Size5dp = 5.dp
+val Size6dp = 6.dp
val Size10dp = 10.dp
val Size12dp = 12.dp
val Size13dp = 13.dp
diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml
index fda50f136..9e9dd9327 100644
--- a/amethyst/src/main/res/values/strings.xml
+++ b/amethyst/src/main/res/values/strings.xml
@@ -21,6 +21,7 @@
Impersonation
Illegal Behavior
Other
+ Harassment
Unknown
Relay Icon
Unknown Author
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 322b3ba5a..efeb84b50 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,7 +1,7 @@
[versions]
accompanistAdaptive = "0.37.2"
activityCompose = "1.10.1"
-agp = "8.8.2"
+agp = "8.9.0"
android-compileSdk = "35"
android-minSdk = "26"
android-targetSdk = "35"
@@ -13,10 +13,10 @@ benchmark = "1.3.3"
benchmarkJunit4 = "1.3.3"
biometricKtx = "1.2.0-alpha05"
coil = "3.1.0"
-composeBom = "2025.02.00"
+composeBom = "2025.03.00"
coreKtx = "1.15.0"
espressoCore = "3.6.1"
-firebaseBom = "33.9.0"
+firebaseBom = "33.10.0"
fragmentKtx = "1.8.6"
gms = "4.4.2"
jacksonModuleKotlin = "2.18.2"
@@ -33,9 +33,9 @@ lifecycleRuntimeKtx = "2.8.7"
lightcompressor = "1.3.2"
markdown = "077a2cde64"
media3 = "1.5.1"
-mockk = "1.13.16"
+mockk = "1.13.17"
kotlinx-coroutines-test = "1.10.1"
-navigationCompose = "2.8.8"
+navigationCompose = "2.8.9"
okhttp = "5.0.0-alpha.14"
runner = "1.6.2"
rfc3986 = "0.1.2"
@@ -46,9 +46,9 @@ torAndroid = "0.4.8.12"
translate = "17.0.3"
unifiedpush = "2.3.1"
urlDetector = "0.1.23"
-vico-charts = "2.0.1"
+vico-charts = "2.0.3"
zelory = "3.0.1"
-zoomable = "2.1.0"
+zoomable = "2.3.0"
zxing = "3.5.3"
zxingAndroidEmbedded = "4.3.0"
windowCoreAndroid = "1.3.0"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d4209e0f1..17098b30b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Wed Jan 04 09:23:50 EST 2023
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt
index 5f335b67b..6e69131b7 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.audio.header.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class DownloadUrlTag {
companion object {
const val TAG_NAME = "download_url"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt
index d3bc35de7..2e896aa98 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.audio.header.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class StreamUrlTag {
companion object {
const val TAG_NAME = "stream_url"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt
index 0942299f2..491653632 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt
@@ -21,7 +21,9 @@
package com.vitorpamplona.quartz.experimental.audio.header.tags
import com.fasterxml.jackson.module.kotlin.readValue
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
+import com.vitorpamplona.quartz.utils.ensure
class WaveformTag(
val wave: List,
@@ -30,12 +32,12 @@ class WaveformTag(
companion object {
const val TAG_NAME = "waveform"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): WaveformTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- if (tag[1].isEmpty()) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
val wave = runCatching { EventMapper.mapper.readValue>(tag[1]) }.getOrNull()
if (wave.isNullOrEmpty()) return null
return WaveformTag(wave)
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt
index e52b81d2e..56488bb95 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.audio.track.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class CoverTag {
companion object {
const val TAG_NAME = "cover"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt
index d620dbe45..7ebaacc27 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.audio.track.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class MediaTag {
companion object {
const val TAG_NAME = "media"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt
index 1701e908f..ebc074a71 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt
@@ -23,9 +23,10 @@ package com.vitorpamplona.quartz.experimental.audio.track.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.isNotName
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
data class ParticipantTag(
@@ -36,26 +37,27 @@ data class ParticipantTag(
companion object {
const val TAG_NAME = "p"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Tag): ParticipantTag? {
- if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return ParticipantTag(tag[1], tag.getOrNull(2))
}
@JvmStatic
fun parseKey(tag: Tag): String? {
- if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return tag[1]
}
@JvmStatic
fun assemble(
pubkey: HexKey,
- relayHint: String?,
+ relayHint: String? = null,
) = arrayOfNotNull(TAG_NAME, pubkey, relayHint)
}
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt
index c0439e8df..dde2c19ae 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.audio.track.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class PriceTag {
companion object {
const val TAG_NAME = "price"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt
index fd3a227c6..34bf125a3 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.audio.track.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class TypeTag {
companion object {
const val TAG_NAME = "c"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt
index e8f3acd97..cb514b1d7 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt
@@ -20,16 +20,19 @@
*/
package com.vitorpamplona.quartz.experimental.bounties
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
import java.math.BigDecimal
class RewardTag {
companion object {
const val TAG_NAME = "reward"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): BigDecimal? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return runCatching { BigDecimal(tag[1]) }.getOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt
new file mode 100644
index 000000000..8d7ca91f2
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.experimental.decoupling
+
+import com.vitorpamplona.quartz.experimental.decoupling.setup.EncryptionKeyListEvent
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
+import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
+import com.vitorpamplona.quartz.nip44Encryption.Nip44
+
+class DecoupledCipher {
+ fun innerEncrypt(
+ content: String,
+ privKey: ByteArray,
+ toPublicKey: HexKey,
+ ) = Nip44
+ .encrypt(content, privKey, toPublicKey.hexToByteArray())
+ .encodePayload()
+
+ fun innerDecrypt(
+ ciphertext: String,
+ privKey: ByteArray,
+ fromPublicKey: HexKey,
+ ) = Nip44.decrypt(
+ payload = ciphertext,
+ privateKey = privKey,
+ pubKey = fromPublicKey.hexToByteArray(),
+ )
+
+ fun encrypt(
+ decryptedContent: String,
+ toPublicKey: HexKey,
+ fromKeyList: EncryptionKeyListEvent,
+ toKeyList: EncryptionKeyListEvent,
+ signer: NostrSigner,
+ onReady: (String) -> Unit,
+ ) {
+ val toKeys = toKeyList.keys()
+ val sendToKey = if (toKeys.isEmpty()) toKeyList.pubKey else toKeys.random().pubkey
+
+ val fromKeys = fromKeyList.keys()
+
+ // uses the main key
+ if (fromKeys.isEmpty()) {
+ signer.nip44Encrypt(decryptedContent, sendToKey, onReady)
+ } else {
+ val keyToUse = fromKeys.random()
+
+ EncryptionKeyCache.getOrLoad(
+ deriveFromPubKey = signer.pubKey,
+ nonce = keyToUse.nonce,
+ load = { onLoaded ->
+ signer.deriveKey(keyToUse.nonce) { newPrivKey ->
+ onLoaded(newPrivKey.hexToByteArray())
+ }
+ },
+ ) { derivedPrivKey ->
+ onReady(innerEncrypt(decryptedContent, derivedPrivKey, sendToKey))
+ }
+ }
+ }
+
+ fun decrypt(
+ encryptedContent: String,
+ fromPublicKey: HexKey,
+ toPublicKey: HexKey,
+ fromKeyList: EncryptionKeyListEvent,
+ toEncryptedKeyList: EncryptionKeyListEvent,
+ signer: NostrSigner,
+ onReady: (String) -> Unit,
+ ) {
+ val fromKeys = fromKeyList.keys()
+ val sentFromKey = if (fromKeys.isEmpty()) fromKeyList.pubKey else fromKeys.random().pubkey
+
+ val keyToUse = toEncryptedKeyList.keys().firstOrNull { it.pubkey == toPublicKey }
+
+ // uses the main key
+ if (signer.pubKey == toPublicKey) {
+ signer.nip44Decrypt(encryptedContent, sentFromKey, onReady)
+ } else if (keyToUse != null) {
+ EncryptionKeyCache.getOrLoad(
+ deriveFromPubKey = signer.pubKey,
+ nonce = keyToUse.nonce,
+ load = { onLoaded ->
+ signer.deriveKey(keyToUse.nonce) { newPrivKey ->
+ onLoaded(newPrivKey.hexToByteArray())
+ }
+ },
+ ) { derivedPrivKey ->
+ innerDecrypt(encryptedContent, derivedPrivKey, sentFromKey)?.let { onReady(it) }
+ }
+ }
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt
new file mode 100644
index 000000000..0cddc52af
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.experimental.decoupling
+
+import android.util.LruCache
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+
+object EncryptionKeyCache {
+ private val sharedNonceKeyCache = LruCache(50)
+
+ private fun idx(
+ key: HexKey,
+ nonce: HexKey,
+ ) = EncryptionKeyCacheKey(key, nonce)
+
+ fun put(
+ deriveFromPubKey: HexKey,
+ nonce: HexKey,
+ privKey: ByteArray,
+ ) = sharedNonceKeyCache.put(idx(deriveFromPubKey, nonce), privKey)
+
+ fun get(
+ deriveFromPubKey: HexKey,
+ nonce: HexKey,
+ ) = sharedNonceKeyCache.get(idx(deriveFromPubKey, nonce))
+
+ inline fun getOrLoad(
+ deriveFromPubKey: HexKey,
+ nonce: HexKey,
+ load: (onLoaded: (privKey: ByteArray) -> Unit) -> Unit,
+ crossinline whenReady: (privKey: ByteArray) -> Unit,
+ ) {
+ val cachedPrivKey = get(deriveFromPubKey, nonce)
+ if (cachedPrivKey != null) {
+ whenReady(cachedPrivKey)
+ return
+ }
+
+ load { newPrivKey ->
+ put(deriveFromPubKey, nonce, newPrivKey)
+ whenReady(newPrivKey)
+ }
+ }
+}
+
+// keeps a cache per signed-in user
+private data class EncryptionKeyCacheKey(
+ val pubkey: HexKey,
+ val nonce: HexKey,
+)
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt
new file mode 100644
index 000000000..15fbbad7a
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.experimental.decoupling
+
+import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
+import com.vitorpamplona.quartz.nip44Encryption.crypto.Hkdf
+import com.vitorpamplona.quartz.utils.sha256.sha256
+
+class EncryptionKeyDerivation {
+ companion object {
+ private val saltPrefix = "nip44kd".toByteArray(Charsets.UTF_8)
+
+ fun derivePrivateKey(
+ privKey: ByteArray,
+ nonce: ByteArray?,
+ ): ByteArray {
+ if (nonce == null) {
+ return privKey
+ }
+ return sha256(Hkdf().extract(privKey, saltPrefix + nonce))
+ }
+
+ fun deriveKeyPair(
+ privKey: ByteArray,
+ nonce: ByteArray?,
+ ): KeyPair = KeyPair(privKey = derivePrivateKey(privKey, nonce))
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt
new file mode 100644
index 000000000..e55232c0c
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.experimental.decoupling.setup
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.experimental.decoupling.setup.tags.KeyTag
+import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
+import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
+import com.vitorpamplona.quartz.nip01Core.signers.eventUpdate
+import com.vitorpamplona.quartz.nip31Alts.alt
+import com.vitorpamplona.quartz.utils.TimeUtils
+
+@Immutable
+class EncryptionKeyListEvent(
+ id: HexKey,
+ pubKey: HexKey,
+ createdAt: Long,
+ tags: Array>,
+ content: String,
+ sig: HexKey,
+) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
+ fun keys() = tags.mapNotNull(KeyTag::parse)
+
+ companion object {
+ const val KIND = 10044
+ const val ALT = "Encryption keys"
+
+ fun add(
+ key: KeyTag,
+ currentEvent: EncryptionKeyListEvent,
+ createdAt: Long = TimeUtils.now(),
+ initializer: TagArrayBuilder.() -> Unit = {},
+ ) = eventUpdate(currentEvent, createdAt) {
+ alt(ALT)
+ key(key)
+ initializer()
+ }
+
+ fun remove(
+ key: KeyTag,
+ currentEvent: EncryptionKeyListEvent,
+ createdAt: Long = TimeUtils.now(),
+ initializer: TagArrayBuilder.() -> Unit = {},
+ ) = eventUpdate(currentEvent, createdAt) {
+ alt(ALT)
+ removeIf(KeyTag::isSameKey, key.toTagArray())
+ initializer()
+ }
+
+ fun build(
+ keys: List,
+ createdAt: Long = TimeUtils.now(),
+ initializer: TagArrayBuilder.() -> Unit = {},
+ ) = eventTemplate(KIND, "", createdAt) {
+ alt(ALT)
+ keys(keys)
+ initializer()
+ }
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt
new file mode 100644
index 000000000..a2a761236
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.experimental.decoupling.setup
+
+import com.vitorpamplona.quartz.experimental.decoupling.setup.tags.KeyTag
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
+
+fun TagArrayBuilder.key(
+ key: HexKey,
+ nonce: HexKey,
+) = add(KeyTag.assemble(key, nonce))
+
+fun TagArrayBuilder.key(key: KeyTag) = add(key.toTagArray())
+
+fun TagArrayBuilder.keys(keys: List) = addAll(keys.map { it.toTagArray() })
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt
new file mode 100644
index 000000000..4cc4a1707
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.experimental.decoupling.setup.tags
+
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
+class KeyTag(
+ val pubkey: HexKey,
+ val nonce: HexKey,
+) {
+ fun toTagArray() = assemble(pubkey, nonce)
+
+ companion object {
+ const val TAG_NAME = "n"
+
+ fun isSameKey(
+ tag1: Array,
+ tag2: Array,
+ ): Boolean {
+ ensure(tag1.has(1)) { return false }
+ ensure(tag2.has(1)) { return false }
+ ensure(tag1[0] == tag2[0]) { return false }
+ ensure(tag1[1] == tag2[1]) { return false }
+ return true
+ }
+
+ @JvmStatic
+ fun parse(tag: Array): KeyTag? {
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
+ return KeyTag(tag[1], tag[2])
+ }
+
+ @JvmStatic
+ fun assemble(
+ key: HexKey,
+ nonce: HexKey,
+ ) = arrayOf(TAG_NAME, key, nonce)
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/PrivateOutboxRelayListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/PrivateOutboxRelayListEvent.kt
index bd418eec3..624e101e6 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/PrivateOutboxRelayListEvent.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/PrivateOutboxRelayListEvent.kt
@@ -47,6 +47,8 @@ class PrivateOutboxRelayListEvent(
) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient private var privateTagsCache: Array>? = null
+ override fun isContentEncoded() = true
+
override fun countMemory(): Long =
super.countMemory() +
pointerSizeInBytes + (privateTagsCache?.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } ?: 0)
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt
index 9a3217b0e..fa1a7b02e 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt
@@ -24,7 +24,7 @@ import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag.MARKER
fun MarkedETag.Companion.parseFork(tag: Array): MarkedETag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ if (tag.size < 4 || tag[0] != "e") return null
if (tag[ORDER_MARKER] != MARKER.FORK.code) return null
// ["e", id hex, relay hint, marker, pubkey]
return MarkedETag(
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt
index c09d4e768..28a5eaf8d 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt
@@ -20,6 +20,9 @@
*/
package com.vitorpamplona.quartz.experimental.interactiveStories.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class ReadStatusTag {
enum class STATUS(
val value: String,
@@ -34,11 +37,12 @@ class ReadStatusTag {
companion object {
const val TAG_NAME = "status"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt
index 01062321e..ae354c2d0 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt
@@ -60,7 +60,6 @@ data class RootSceneTag(
companion object {
const val TAG_NAME = "A"
- const val TAG_SIZE = 2
fun assembleATagId(
kind: Int,
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt
index 652e68129..a17697642 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt
@@ -35,7 +35,6 @@ class StoryOptionTag(
companion object {
const val TAG_NAME = "option"
- const val TAG_SIZE = 3
@JvmStatic
fun parse(tag: Array): StoryOptionTag? {
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt
index c8f7ded13..f6b2024f3 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.nns.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class IPv4Tag {
companion object {
const val TAG_NAME = "ip4"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt
index 07086b274..db673d7db 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.nns.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class IPv6Tag {
companion object {
const val TAG_NAME = "ip6"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt
index 2eca2f76c..7de43e61d 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.nns.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class VersionTag {
companion object {
const val TAG_NAME = "version"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetnameTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetnameTag.kt
index df1ccfbfc..ea9b4981a 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetnameTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetnameTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.relationshipStatus.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class PetnameTag {
companion object {
const val TAG_NAME = "petname"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt
index 3525445d0..b39bce3ae 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.relationshipStatus.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class RankTag {
companion object {
const val TAG_NAME = "rank"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Int? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toIntOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt
index 8a7696c29..058c58a74 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.relationshipStatus.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class SummaryTag {
companion object {
const val TAG_NAME = "summary"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt
index 5b07019a8..21883f052 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.zapPolls.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class ClosedAtTag {
companion object {
const val TAG_NAME = "closed_at"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt
index 87e3967ec..b1d2fce00 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt
@@ -20,16 +20,19 @@
*/
package com.vitorpamplona.quartz.experimental.zapPolls.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
import kotlin.math.round
class ConsensusThresholdTag {
companion object {
const val TAG_NAME = "consensus_threshold"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Double? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()?.toDouble()?.div(100)
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt
index bf6381dee..286c9876d 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.zapPolls.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class MaximumTag {
companion object {
const val TAG_NAME = "value_maximum"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt
index 7f1b8cfe8..5a1fb5ed8 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.experimental.zapPolls.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class MinimumTag {
companion object {
const val TAG_NAME = "value_minimum"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt
index f5a74b06d..1ab922f5f 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt
@@ -20,6 +20,9 @@
*/
package com.vitorpamplona.quartz.experimental.zapPolls.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class PollOptionTag(
val index: Int,
val descriptor: String,
@@ -28,11 +31,13 @@ class PollOptionTag(
companion object {
const val TAG_NAME = "poll_option"
- const val TAG_SIZE = 3
@JvmStatic
fun parse(tag: Array): PollOptionTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
val index = tag[1].toIntOrNull() ?: return null
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt
index d97acc9c1..d9273988a 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt
@@ -27,64 +27,12 @@ typealias Tag = Array
*/
fun Tag.has(index: Int) = size > index
-/**
- * Returns if the Tag has less than $index elements.
- */
-fun Tag.lacks(index: Int) = size <= index
-
fun Tag.name() = this[0]
fun Tag.value() = this[1]
-fun Tag.hasValue() = this[1].isNotEmpty()
+fun Tag.hasValue() = if (has(1)) this[1].isNotEmpty() else false
-fun Tag.nameOrNull() = if (size > 0) name() else null
+fun Tag.nameOrNull() = if (has(0)) this[0] else null
-fun Tag.valueOrNull() = if (size > 1) value() else null
-
-fun Tag.isNameUnsafe(name: String): Boolean = name() == name
-
-fun Tag.isValueUnsafe(value: String): Boolean = value() == value
-
-fun Tag.isValueInUnsafe(values: Set): Boolean = value() in values
-
-fun Tag.match(name: String): Boolean = if (size > 0) isNameUnsafe(name) else false
-
-fun Tag.isValue(value: String): Boolean = if (size > 1) isValueUnsafe(value) else false
-
-fun Tag.match(
- name: String,
- value: String,
- minSize: Int,
-): Boolean = if (size >= minSize) isNameUnsafe(name) && isValueUnsafe(value) else false
-
-fun Tag.match(
- name: String,
- values: Set,
- minSize: Int,
-): Boolean = if (size >= minSize) isNameUnsafe(name) && isValueInUnsafe(values) else false
-
-fun Tag.match(
- name: String,
- minSize: Int,
-): Boolean = if (size >= minSize) isNameUnsafe(name) else false
-
-fun Tag.isNotName(
- name: String,
- minSize: Int,
-): Boolean = !match(name, minSize)
-
-fun Tag.matchAndHasValue(
- name: String,
- minSize: Int,
-): Boolean = if (size >= minSize) isNameUnsafe(name) && hasValue() else false
-
-fun Tag.valueIfMatches(
- name: String,
- minSize: Int,
-): String? = if (match(name, minSize)) value() else null
-
-fun Tag.valueToIntIfMatches(
- name: String,
- minSize: Int,
-): Int? = if (match(name, minSize)) value().toIntOrNull() else null
+fun Tag.valueOrNull() = if (has(1)) this[1] else null
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt
index 3d03f667b..aab802bed 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt
@@ -35,7 +35,7 @@ class TagArrayBuilder {
tagName: String,
tagValue: String,
): TagArrayBuilder {
- tagList[tagName]?.removeIf { it.value() == tagValue }
+ tagList[tagName]?.removeIf { it.valueOrNull() == tagValue }
if (tagList[tagName]?.isEmpty() == true) {
tagList.remove(tagName)
}
@@ -46,18 +46,8 @@ class TagArrayBuilder {
predicate: (Tag, Tag) -> Boolean,
toCompare: Tag,
): TagArrayBuilder {
- tagList[toCompare.name()]?.removeIf { predicate(it, toCompare) }
- if (tagList[toCompare.name()]?.isEmpty() == true) {
- tagList.remove(toCompare.name())
- }
- return this
- }
-
- fun removeIf(
- tagName: String,
- tagValue: String,
- ): TagArrayBuilder {
- tagList[tagName]?.removeIf { it.value() == tagValue }
+ val tagName = toCompare.nameOrNull() ?: return this
+ tagList[tagName]?.removeIf { predicate(it, toCompare) }
if (tagList[tagName]?.isEmpty() == true) {
tagList.remove(tagName)
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/KeyPair.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/KeyPair.kt
index 048309e56..0fd06d11d 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/KeyPair.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/KeyPair.kt
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.toHexKey
class KeyPair(
privKey: ByteArray? = null,
pubKey: ByteArray? = null,
+ forceReplacePubkey: Boolean = true,
) {
val privKey: ByteArray?
val pubKey: ByteArray
@@ -44,7 +45,11 @@ class KeyPair(
} else {
// as private key is provided, ignore the public key and set keys according to private key
this.privKey = privKey
- this.pubKey = Nip01.pubKeyCreate(privKey)
+ if (pubKey == null || forceReplacePubkey) {
+ this.pubKey = Nip01.pubKeyCreate(privKey)
+ } else {
+ this.pubKey = pubKey
+ }
}
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt
index cb9e70b98..d5f401c9b 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt
@@ -73,6 +73,11 @@ abstract class NostrSigner(
onReady: (LnZapPrivateEvent) -> Unit,
)
+ abstract fun deriveKey(
+ nonce: HexKey,
+ onReady: (HexKey) -> Unit,
+ )
+
fun decrypt(
encryptedContent: String,
fromPublicKey: HexKey,
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt
index c49e52bc2..a2d568d6e 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt
@@ -80,4 +80,11 @@ class NostrSignerInternal(
) {
signerSync.decryptZapEvent(event)?.let { onReady(it) }
}
+
+ override fun deriveKey(
+ nonce: HexKey,
+ onReady: (HexKey) -> Unit,
+ ) {
+ signerSync.deriveKey(nonce)?.let { onReady(it) }
+ }
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt
index 112e0eae4..3376d70fc 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt
@@ -21,6 +21,7 @@
package com.vitorpamplona.quartz.nip01Core.signers
import android.util.Log
+import com.vitorpamplona.quartz.experimental.decoupling.EncryptionKeyDerivation
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
@@ -128,4 +129,10 @@ class NostrSignerSync(
}
fun decryptZapEvent(event: LnZapRequestEvent): LnZapPrivateEvent? = PrivateZapRequestBuilder().decryptZapEvent(event, this)
+
+ fun deriveKey(nonce: HexKey): HexKey? {
+ if (keyPair.privKey == null) return null
+
+ return EncryptionKeyDerivation.derivePrivateKey(keyPair.privKey, nonce.hexToByteArray()).toHexKey()
+ }
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt
index 2f8e464d1..07ba6ac59 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt
@@ -23,9 +23,6 @@ package com.vitorpamplona.quartz.nip01Core.tags.addressables
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.has
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.name
-import com.vitorpamplona.quartz.nip01Core.core.value
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
@@ -57,40 +54,44 @@ data class ATag(
companion object {
const val TAG_NAME = "a"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTagged(tag: Array) = tag.size >= 2 && tag[0] == TAG_NAME && tag[1].isNotEmpty()
+ fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
- @JvmStatic
fun isSameAddress(
tag1: Array,
tag2: Array,
- ) = tag1.match(tag2.name(), tag2.value(), TAG_SIZE)
+ ): Boolean {
+ ensure(tag1.has(1)) { return false }
+ ensure(tag2.has(1)) { return false }
+ ensure(tag1[0] == tag2[0]) { return false }
+ ensure(tag1[1] == tag2[1]) { return false }
+ return true
+ }
@JvmStatic
fun isTagged(
tag: Array,
addressId: String,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] == addressId
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId
@JvmStatic
fun isTagged(
tag: Array,
address: ATag,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] == address.toTag()
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == address.toTag()
@JvmStatic
fun isIn(
tag: Array,
addressIds: Set,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in addressIds
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds
@JvmStatic
fun isTaggedWithKind(
tag: Array,
kind: String,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && Address.isOfKind(tag[1], kind)
+ ) = tag.has(1) && tag[0] == TAG_NAME && Address.isOfKind(tag[1], kind)
@JvmStatic
fun parseIfOfKind(
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt
index 0f59c7897..de7942709 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt
@@ -22,10 +22,12 @@ package com.vitorpamplona.quartz.nip01Core.tags.events
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
+import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
@@ -56,32 +58,38 @@ data class ETag(
companion object {
const val TAG_NAME = "e"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTagged(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].length == 64
+ fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].length == 64
@JvmStatic
fun isTagged(
tag: Array,
eventId: HexKey,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] == eventId
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId
@JvmStatic
fun parse(tag: Array): ETag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return ETag(tag[1], tag.getOrNull(2), tag.getOrNull(3))
}
@JvmStatic
fun parseId(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return tag[1]
}
@JvmStatic
fun parseAsHint(tag: Array): EventIdHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return EventIdHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt
index 773b14db2..ac11b4d46 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt
@@ -20,17 +20,21 @@
*/
package com.vitorpamplona.quartz.nip01Core.tags.hashtags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class HashtagTag {
companion object {
const val TAG_NAME = "t"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTagged(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty()
+ fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].isEmpty()) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt
index 051199464..c076c4dee 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt
@@ -20,26 +20,30 @@
*/
package com.vitorpamplona.quartz.nip01Core.tags.kinds
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class KindTag {
companion object {
const val TAG_NAME = "k"
- const val TAG_SIZE = 2
- fun match(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
+ fun match(tag: Array) = tag.has(1) && tag[0] == TAG_NAME
fun isTagged(
tag: Array,
kind: String,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] == kind
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == kind
fun isIn(
tag: Array,
kinds: Set,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in kinds
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in kinds
@JvmStatic
fun parse(tag: Array): Int? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toInt()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt
index 55a2270ac..53e941df9 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt
@@ -24,12 +24,14 @@ import android.util.Log
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
+import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
@@ -50,24 +52,25 @@ data class PTag(
companion object {
const val TAG_NAME = "p"
- const val TAG_SIZE = 2
fun isTagged(
tag: Array,
key: HexKey,
- ): Boolean = tag.size >= 2 && tag[0] == TAG_NAME && tag[1] == key
+ ): Boolean = tag.has(1) && tag[0] == TAG_NAME && tag[1] == key
@JvmStatic
fun parse(tag: Tag): PTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return PTag(tag[1], tag.getOrNull(2))
}
@JvmStatic
fun parseKey(tag: Array): HexKey? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- if (tag[1].length != 64) {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) {
Log.w("PTag", "Invalid `$TAG_NAME` value ${tag.joinToString(", ")}")
return null
}
@@ -76,7 +79,10 @@ data class PTag(
@JvmStatic
fun parseAsHint(tag: Array): PubKeyHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return PubKeyHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt
index 8c07af9ff..1ad8b8f29 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt
@@ -20,34 +20,38 @@
*/
package com.vitorpamplona.quartz.nip01Core.tags.references
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip96FileStorage.HttpUrlFormatter
+import com.vitorpamplona.quartz.utils.ensure
class ReferenceTag {
companion object {
const val TAG_NAME = "r"
- const val TAG_SIZE = 2
@JvmStatic
fun isTagged(
tag: Array,
reference: String,
- ): Boolean = tag.size >= 2 && tag[0] == TAG_NAME && tag[1] == reference
+ ): Boolean = tag.has(1) && tag[0] == TAG_NAME && tag[1] == reference
@JvmStatic
fun isIn(
tag: Array,
references: Set,
- ): Boolean = tag.size >= 2 && tag[0] == TAG_NAME && tag[1] in references
+ ): Boolean = tag.has(1) && tag[0] == TAG_NAME && tag[1] in references
@JvmStatic
fun hasReference(tag: Array): Boolean {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return false
+ ensure(tag.has(1)) { return false }
+ ensure(tag[0] == TAG_NAME) { return false }
return tag[1].isNotEmpty()
}
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt
index eab66cef3..d40a858c7 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt
@@ -23,11 +23,13 @@ package com.vitorpamplona.quartz.nip02FollowList.tags
import android.util.Log
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
+import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
@@ -56,20 +58,23 @@ data class ContactTag(
companion object {
const val TAG_NAME = "p"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTagged(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty()
+ fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array): ContactTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return ContactTag(tag[1], tag.getOrNull(2), tag.getOrNull(3))
}
@JvmStatic
fun parseValid(tag: Array): ContactTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return try {
ContactTag(decodePublicKey(tag[1]).toHexKey(), tag.getOrNull(2), tag.getOrNull(3))
} catch (e: Exception) {
@@ -80,13 +85,17 @@ data class ContactTag(
@JvmStatic
fun parseKey(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return tag[1]
}
@JvmStatic
fun parseValidKey(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return try {
decodePublicKey(tag[1]).toHexKey()
} catch (e: Exception) {
@@ -97,7 +106,10 @@ data class ContactTag(
@JvmStatic
fun parseAsHint(tag: Array): PubKeyHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return PubKeyHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt
index dc150479c..15ecd3494 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt
@@ -22,11 +22,13 @@ package com.vitorpamplona.quartz.nip10Notes.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
import com.vitorpamplona.quartz.nip01Core.tags.events.GenericETag
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
+import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
@@ -71,7 +73,7 @@ data class MarkedETag(
companion object {
const val TAG_NAME = "e"
- const val TAG_SIZE = 4
+ private const val TAG_SIZE = 4
const val ORDER_NAME = 0
const val ORDER_EVT_ID = 1
@@ -183,7 +185,10 @@ data class MarkedETag(
@JvmStatic
fun parseAsHint(tag: Array): EventIdHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return EventIdHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt
index 7b0947b38..d21e0d341 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt
@@ -48,9 +48,9 @@ fun List.positionalMarkedTags(
}
}.map {
when (it) {
- root -> arrayOf(tagName, it, "", "root")
- replyingTo -> arrayOf(tagName, it, "", "reply")
- forkedFrom -> arrayOf(tagName, it, "", "fork")
+ root -> arrayOf(tagName, it, "", MarkedETag.MARKER.ROOT)
+ replyingTo -> arrayOf(tagName, it, "", MarkedETag.MARKER.REPLY)
+ forkedFrom -> arrayOf(tagName, it, "", MarkedETag.MARKER.FORK)
else -> arrayOf(tagName, it)
}
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt
index 8c2fd5510..8b2674324 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt
@@ -20,8 +20,10 @@
*/
package com.vitorpamplona.quartz.nip13Pow.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
+import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class PoWTag(
@@ -34,20 +36,23 @@ class PoWTag(
companion object {
const val TAG_NAME = "nonce"
- const val TAG_SIZE = 2
@JvmStatic
- fun hasTagWithContent(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty()
+ fun hasTagWithContent(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array): PoWTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return PoWTag(tag[1], tag.getOrNull(2)?.toIntOrNull())
}
@JvmStatic
fun parseCommitment(tag: Array): Int? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag.getOrNull(2)?.toIntOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt
index 8489b1669..4d8becbb8 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt
@@ -20,17 +20,21 @@
*/
package com.vitorpamplona.quartz.nip14Subject
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class SubjectTag {
companion object {
const val TAG_NAME = "subject"
- const val TAG_SIZE = 2
@JvmStatic
- fun hasTagWithContent(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1].isNotEmpty()
+ fun hasTagWithContent(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt
index a44727038..8fe35ea2d 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt
@@ -20,17 +20,21 @@
*/
package com.vitorpamplona.quartz.nip17Dm.files.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class EncryptionAlgo {
companion object {
const val TAG_NAME = "encryption-algorithm"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
+ fun isTag(tag: Array) = tag.has(1) && tag[0] == TAG_NAME
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt
index d3ed47c41..6dbfdf91b 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt
@@ -20,20 +20,23 @@
*/
package com.vitorpamplona.quartz.nip17Dm.files.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
+import com.vitorpamplona.quartz.utils.ensure
class EncryptionKey {
companion object {
const val TAG_NAME = "decryption-key"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
+ fun isTag(tag: Array) = tag.has(0) && tag[0] == TAG_NAME
@JvmStatic
fun parse(tag: Array): ByteArray? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return runCatching { tag[1].hexToByteArray() }.getOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt
index b34eb627e..1a792a3df 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt
@@ -20,20 +20,23 @@
*/
package com.vitorpamplona.quartz.nip17Dm.files.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
+import com.vitorpamplona.quartz.utils.ensure
class EncryptionNonce {
companion object {
const val TAG_NAME = "decryption-nonce"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
+ fun isTag(tag: Array) = tag.has(0) && tag[0] == TAG_NAME
@JvmStatic
fun parse(tag: Array): ByteArray? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return runCatching { tag[1].hexToByteArray() }.getOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt
index 38620fd2d..3cae4c9d3 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt
@@ -20,19 +20,23 @@
*/
package com.vitorpamplona.quartz.nip17Dm.files.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class FileTypeTag {
companion object {
const val TAG_NAME = "file-type"
- const val TAG_SIZE = 2
fun isIn(
tag: Array,
mimeTypes: Set,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in mimeTypes
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in mimeTypes
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt
index a0e4ac3a8..cc78b6f5d 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt
@@ -59,13 +59,13 @@ data class QAddressableTag(
override fun toTagArray() = assemble(address, relay)
companion object {
- const val TAG_SIZE = 2
+ const val TAG_NAME = "q"
@JvmStatic
fun parse(tag: Array): QAddressableTag? {
ensure(tag.has(1)) { return null }
- ensure(tag[0] == QTag.TAG_NAME) { return null }
- ensure(tag[1].isNotEmpty()) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length != 64) { return null }
val address = Address.parse(tag[1]) ?: return null
return QAddressableTag(address, tag.getOrNull(2))
}
@@ -76,12 +76,12 @@ data class QAddressableTag(
pubKeyHex: HexKey,
dTag: String,
relay: String?,
- ) = arrayOfNotNull(QTag.TAG_NAME, Address.assemble(kind, pubKeyHex, dTag), relay)
+ ) = arrayOfNotNull(TAG_NAME, Address.assemble(kind, pubKeyHex, dTag), relay)
@JvmStatic
fun assemble(
address: Address,
relay: String?,
- ) = arrayOfNotNull(QTag.TAG_NAME, address.toValue(), relay)
+ ) = arrayOfNotNull(TAG_NAME, address.toValue(), relay)
}
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt
index 511bb12d0..995890fd0 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt
@@ -22,8 +22,10 @@ package com.vitorpamplona.quartz.nip18Reposts.quotes
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
+import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
@@ -47,11 +49,13 @@ data class QEventTag(
override fun toTagArray() = assemble(eventId, relay, author)
companion object {
- const val TAG_SIZE = 2
+ const val TAG_NAME = "q"
@JvmStatic
fun parse(tag: Array): QEventTag? {
- if (tag.size < TAG_SIZE || tag[0] != QTag.TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return QEventTag(tag[1], tag.getOrNull(2), tag.getOrNull(3))
}
@@ -60,6 +64,6 @@ data class QEventTag(
eventId: HexKey,
relay: String?,
author: HexKey?,
- ) = arrayOfNotNull(QTag.TAG_NAME, eventId, relay, author)
+ ) = arrayOfNotNull(TAG_NAME, eventId, relay, author)
}
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt
index 52e6d1490..5feeab571 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt
@@ -20,35 +20,46 @@
*/
package com.vitorpamplona.quartz.nip18Reposts.quotes
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
+import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
+import com.vitorpamplona.quartz.utils.ensure
interface QTag {
fun toTagArray(): Array
companion object {
const val TAG_NAME = "q"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): QTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
return if (tag[1].length == 64) {
- QEventTag.parse(tag)
+ QEventTag(tag[1], tag.getOrNull(2), tag.getOrNull(3))
} else {
- QAddressableTag.parse(tag)
+ val address = Address.parse(tag[1]) ?: return null
+ QAddressableTag(address, tag.getOrNull(2))
}
}
@JvmStatic
fun parseEventAsHint(tag: Array): EventIdHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return EventIdHint(tag[1], tag[2])
}
@JvmStatic
fun parseAddressAsHint(tag: Array): AddressHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length == 64 || !tag[1].contains(':') || tag[2].isEmpty()) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length != 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
+ ensure(!tag[1].contains(':')) { return null }
return AddressHint(tag[1], tag[2])
}
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt
index 83e94ac49..14410a207 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt
@@ -107,8 +107,8 @@ class CommentEvent(
?: tags.lastNotNullOfOrNull(RootEventTag::parseKey)
fun replyingToAddressId(): String? =
- tags.lastNotNullOfOrNull(RootAddressTag::parseAddress)
- ?: tags.lastNotNullOfOrNull(ReplyAddressTag::parseAddress)
+ tags.lastNotNullOfOrNull(RootAddressTag::parseAddressId)
+ ?: tags.lastNotNullOfOrNull(ReplyAddressTag::parseAddressId)
override fun replyingToAddressOrEvent(): HexKey? = replyingToAddressId() ?: replyingTo()
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt
index 9e930bbf3..43933ee44 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt
@@ -23,11 +23,11 @@ package com.vitorpamplona.quartz.nip22Comments.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
class ReplyAddressTag(
@@ -38,23 +38,32 @@ class ReplyAddressTag(
companion object {
const val TAG_NAME = "a"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array): ReplyAddressTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return ReplyAddressTag(tag[1], tag.getOrNull(2))
}
@JvmStatic
- fun parseAddress(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
+ fun parseAddressId(tag: Array): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ return tag[1]
+ }
@JvmStatic
fun parseAsHint(tag: Array): AddressHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length == 64 || !tag[1].contains(':') || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return AddressHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt
index 1f647c2d5..c6e8598d7 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt
@@ -23,10 +23,11 @@ package com.vitorpamplona.quartz.nip22Comments.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.match
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
data class ReplyAuthorTag(
@@ -37,28 +38,32 @@ data class ReplyAuthorTag(
companion object {
const val TAG_NAME = "p"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Tag): ReplyAuthorTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return ReplyAuthorTag(tag[1], tag.getOrNull(2))
}
@JvmStatic
fun parseKey(tag: Tag): HexKey? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return tag[1]
}
@JvmStatic
fun parseAsHint(tag: Array): PubKeyHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return PubKeyHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt
index 77f68fbc4..337e6f35b 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt
@@ -23,11 +23,12 @@ package com.vitorpamplona.quartz.nip22Comments.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
import com.vitorpamplona.quartz.nip01Core.tags.events.EventReference
+import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
class ReplyEventTag(
@@ -39,42 +40,53 @@ class ReplyEventTag(
companion object {
const val TAG_NAME = "e"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun isTagged(
tag: Array,
eventId: String,
- ) = tag.match(TAG_NAME, eventId, TAG_SIZE)
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId
@JvmStatic
fun isIn(
tag: Array,
eventIds: Set,
- ) = tag.match(TAG_NAME, eventIds, TAG_SIZE)
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in eventIds
@JvmStatic
fun parse(tag: Array): ReplyEventTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return ReplyEventTag(tag[1], tag.getOrNull(2), tag.getOrNull(3))
}
@JvmStatic
- fun parseKey(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
+ fun parseKey(tag: Array): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ return tag[1]
+ }
@JvmStatic
fun parseValidKey(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(Hex.isHex(tag[1])) { return null }
return tag[1]
}
@JvmStatic
fun parseAsHint(tag: Array): EventIdHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return EventIdHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt
index 9926fa8ff..f0e2299e8 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt
@@ -22,24 +22,28 @@ package com.vitorpamplona.quartz.nip22Comments.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
import com.vitorpamplona.quartz.nip73ExternalIds.GeohashId
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
class ReplyIdentifierTag {
companion object {
const val TAG_NAME = "i"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
- fun parse(tag: Tag) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
+ fun parse(tag: Tag): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ return tag[1]
+ }
@JvmStatic
fun assemble(
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt
index f3a0028ed..43647bcb2 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt
@@ -21,36 +21,36 @@
package com.vitorpamplona.quartz.nip22Comments.tags
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.matchAndHasValue
-import com.vitorpamplona.quartz.nip01Core.core.valueToIntIfMatches
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
+import com.vitorpamplona.quartz.utils.ensure
class ReplyKindTag {
companion object {
const val TAG_NAME = "k"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
-
- @JvmStatic
- fun isTagged(tag: Tag) = tag.matchAndHasValue(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun isTagged(
tag: Tag,
kind: String,
- ) = tag.match(TAG_NAME, kind, TAG_SIZE)
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == kind
@JvmStatic
fun isIn(
tag: Tag,
kinds: Set,
- ) = tag.match(TAG_NAME, kinds, TAG_SIZE)
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in kinds
@JvmStatic
- fun parse(tag: Tag) = tag.valueToIntIfMatches(TAG_NAME, TAG_SIZE)
+ fun parse(tag: Tag): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ return tag[1]
+ }
@JvmStatic
fun assemble(kind: String) = arrayOf(TAG_NAME, kind)
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt
index 3241fbfc6..46263abe2 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt
@@ -24,8 +24,6 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
import com.vitorpamplona.quartz.nip01Core.core.has
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
import com.vitorpamplona.quartz.utils.arrayOfNotNull
@@ -40,26 +38,27 @@ class RootAddressTag(
companion object {
const val TAG_NAME = "A"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun isTagged(
tag: Array,
addressId: String,
- ) = tag.match(TAG_NAME, addressId, TAG_SIZE)
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId
@JvmStatic
fun isIn(
tag: Array,
addressIds: Set,
- ) = tag.match(TAG_NAME, addressIds, TAG_SIZE)
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds
@JvmStatic
fun parse(tag: Array): RootAddressTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return RootAddressTag(tag[1], tag.getOrNull(2))
}
@@ -72,11 +71,19 @@ class RootAddressTag(
}
@JvmStatic
- fun parseAddress(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
+ fun parseAddressId(tag: Array): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ return tag[1]
+ }
@JvmStatic
fun parseAsHint(tag: Array): AddressHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length == 64 || !tag[1].contains(':') || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return AddressHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt
index dcae0d9f9..e548f60bc 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt
@@ -24,11 +24,11 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.PUBKEY_LENGTH
import com.vitorpamplona.quartz.nip01Core.core.Tag
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
import com.vitorpamplona.quartz.utils.arrayOfNotNull
-import kotlin.contracts.ExperimentalContracts
-import kotlin.contracts.contract
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
data class RootAuthorTag(
@@ -41,7 +41,7 @@ data class RootAuthorTag(
const val TAG_NAME = "P"
@JvmStatic
- fun match(tag: Tag) = tag.size > 1 && tag[0] == TAG_NAME
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Tag): ReplyAuthorTag? {
@@ -65,19 +65,13 @@ data class RootAuthorTag(
@JvmStatic
fun parseAsHint(tag: Array): PubKeyHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != PUBKEY_LENGTH || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return PubKeyHint(tag[1], tag[2])
}
- @OptIn(ExperimentalContracts::class)
- inline fun ensure(
- condition: Boolean,
- exit: () -> Nothing,
- ) {
- contract { returns() implies condition }
- if (!condition) exit()
- }
-
@JvmStatic
fun assemble(
pubkey: HexKey,
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt
index c0d546f92..9796e5ec6 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt
@@ -23,11 +23,12 @@ package com.vitorpamplona.quartz.nip22Comments.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
import com.vitorpamplona.quartz.nip01Core.tags.events.EventReference
+import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
class RootEventTag(
@@ -39,42 +40,53 @@ class RootEventTag(
companion object {
const val TAG_NAME = "E"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun isTagged(
tag: Array,
eventId: String,
- ) = tag.match(TAG_NAME, eventId, TAG_SIZE)
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId
@JvmStatic
fun isIn(
tag: Array,
eventIds: Set,
- ) = tag.match(TAG_NAME, eventIds, TAG_SIZE)
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in eventIds
@JvmStatic
fun parse(tag: Array): RootEventTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return RootEventTag(tag[1], tag.getOrNull(2), tag.getOrNull(3))
}
@JvmStatic
- fun parseKey(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
+ fun parseKey(tag: Array): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ return tag[1]
+ }
@JvmStatic
fun parseValidKey(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(Hex.isHex(tag[1])) { return null }
return tag[1]
}
@JvmStatic
fun parseAsHint(tag: Array): EventIdHint? {
- if (tag.size < 3 || tag[0] != TAG_NAME || tag[1].length != 64 || tag[2].isEmpty()) return null
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
return EventIdHint(tag[1], tag[2])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt
index 34aa37a9d..d896b3e66 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt
@@ -22,24 +22,28 @@ package com.vitorpamplona.quartz.nip22Comments.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
import com.vitorpamplona.quartz.nip73ExternalIds.GeohashId
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
class RootIdentifierTag {
companion object {
const val TAG_NAME = "I"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
- fun parse(tag: Tag) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
+ fun parse(tag: Tag): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ return tag[1]
+ }
@JvmStatic
fun assemble(
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt
index bbbe5861d..7a1a31767 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt
@@ -21,20 +21,24 @@
package com.vitorpamplona.quartz.nip22Comments.tags
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
+import com.vitorpamplona.quartz.utils.ensure
class RootKindTag {
companion object {
const val TAG_NAME = "K"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
- fun parse(tag: Tag) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
+ fun parse(tag: Tag): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ return tag[1]
+ }
@JvmStatic
fun assemble(kind: String): Tag = arrayOf(TAG_NAME, kind)
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt
index d7e74286e..0ab6c6714 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip23LongContent.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class ImageTag {
companion object {
const val TAG_NAME = "image"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt
index 671348f27..21f33adb4 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip23LongContent.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class PublishedAtTag {
companion object {
const val TAG_NAME = "published_at"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt
index 1e04d3b56..6acbac79d 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip23LongContent.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class SummaryTag {
companion object {
const val TAG_NAME = "summary"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt
index 409c4eb1d..77b6ab6d6 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip23LongContent.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class TitleTag {
companion object {
const val TAG_NAME = "title"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt
index b28d37465..8a7df1c4a 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt
@@ -20,18 +20,22 @@
*/
package com.vitorpamplona.quartz.nip31Alts
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class AltTag {
companion object {
const val TAG_NAME = "alt"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
@JvmStatic
- fun assemble(altDescriptor: String = "") = arrayOf(TAG_NAME, altDescriptor)
+ fun assemble(altDescriptor: String) = arrayOf(TAG_NAME, altDescriptor)
}
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt
index 47b1e1f18..b4a95634e 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip34Git.repository.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class CloneTag {
companion object {
const val TAG_NAME = "clone"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt
index 4256d7efe..faea7e0d8 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip34Git.repository.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class DescriptionTag {
companion object {
const val TAG_NAME = "description"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt
index 8fc6aff04..147b52d35 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip34Git.repository.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class NameTag {
companion object {
const val TAG_NAME = "name"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt
index 5c5445856..3dae53fa3 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip34Git.repository.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class WebTag {
companion object {
const val TAG_NAME = "web"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt
index 85697ae11..47feee58c 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip35Torrents.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class BtihTag {
companion object {
const val TAG_NAME = "btih"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt
index 895eb423c..0639907fe 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt
@@ -21,7 +21,9 @@
package com.vitorpamplona.quartz.nip35Torrents.tags
import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip46RemoteSigner.getOrNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
class FileTag(
@@ -32,18 +34,21 @@ class FileTag(
companion object {
const val TAG_NAME = "file"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): FileTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return FileTag(tag[1], tag.getOrNull(2)?.toLongOrNull())
}
@JvmStatic
fun parseBytes(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- return tag.getOrNull(2)?.toLongOrNull()
+ ensure(tag.has(2)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
+ return tag[2].toLongOrNull()
}
@JvmStatic
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt
index 744aad94d..1e0786ebf 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip35Torrents.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class InfoHashTag {
companion object {
const val TAG_NAME = "x"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt
index 8282c3670..4e154418e 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip35Torrents.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class TrackerTag {
companion object {
const val TAG_NAME = "tracker"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/CurrentParticipantsTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/CurrentParticipantsTag.kt
index 934a4637b..fb1b44148 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/CurrentParticipantsTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/CurrentParticipantsTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class CurrentParticipantsTag {
companion object {
const val TAG_NAME = "current_participants"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Int? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toIntOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/EndsTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/EndsTag.kt
index 0597b1d54..26d212547 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/EndsTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/EndsTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class EndsTag {
companion object {
const val TAG_NAME = "ends"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt
index a77348d0d..c48c004c3 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/ParticipantTag.kt
@@ -23,9 +23,10 @@ package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.isNotName
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
enum class ROLE(
val code: String,
@@ -43,40 +44,43 @@ data class ParticipantTag(
) : PubKeyReferenceTag {
companion object {
const val TAG_NAME = "p"
- const val TAG_SIZE = 2
fun isIn(
tag: Array,
keys: Set,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in keys
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in keys
@JvmStatic
fun isHost(tag: Tag): Boolean {
- if (tag.isNotName(TAG_NAME, TAG_SIZE)) return false
- if (tag[1].length != 64) return false
- if (tag.getOrNull(3).equals(ROLE.HOST.code)) return true
- return false
+ ensure(tag.has(3)) { return false }
+ ensure(tag[0] == TAG_NAME) { return false }
+ ensure(tag[1].length == 64) { return false }
+ ensure(tag[3] == ROLE.HOST.code) { return false }
+ return true
}
@JvmStatic
fun parse(tag: Tag): ParticipantTag? {
- if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return ParticipantTag(tag[1], tag.getOrNull(2), tag.getOrNull(3), tag.getOrNull(4))
}
@JvmStatic
fun parseHost(tag: Tag): ParticipantTag? {
- if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null
- if (tag[1].length != 64) return null
- if (!tag.getOrNull(3).equals(ROLE.HOST.code)) return null
- return ParticipantTag(tag[1], tag.getOrNull(2), tag.getOrNull(3), tag.getOrNull(4))
+ ensure(tag.has(3)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+ ensure(tag[3] == ROLE.HOST.code) { return null }
+ return ParticipantTag(tag[1], tag[2], tag[3], tag.getOrNull(4))
}
@JvmStatic
fun parseKey(tag: Tag): String? {
- if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/RelayListTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/RelayListTag.kt
index fda64e467..70627f607 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/RelayListTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/RelayListTag.kt
@@ -20,16 +20,20 @@
*/
package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class RelayListTag(
val relayUrls: List,
) {
companion object {
const val TAG_NAME = "relays"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): RelayListTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
val relays =
tag.mapIndexedNotNull { index, s ->
if (index == 0) null else s
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StartsTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StartsTag.kt
index 8c6cb4488..7a094d643 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StartsTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StartsTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class StartsTag {
companion object {
const val TAG_NAME = "starts"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StatusTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StatusTag.kt
index d6e651b9e..f41dcfc05 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StatusTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StatusTag.kt
@@ -20,6 +20,9 @@
*/
package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class StatusTag {
enum class STATUS(
val code: String,
@@ -34,11 +37,12 @@ class StatusTag {
companion object {
const val TAG_NAME = "status"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StreamingTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StreamingTag.kt
index 7303e1a44..667cf742e 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StreamingTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/StreamingTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class StreamingTag {
companion object {
const val TAG_NAME = "streaming"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/TotalParticipantsTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/TotalParticipantsTag.kt
index 52bbeac76..ac34c4bf6 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/TotalParticipantsTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip53LiveActivities/streaming/tags/TotalParticipantsTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class TotalParticipantsTag {
companion object {
const val TAG_NAME = "total_participants"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Int? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toIntOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt
index f375dad42..fd59617c0 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/ExternalSignerLauncher.kt
@@ -46,6 +46,7 @@ enum class SignerType {
NIP44_DECRYPT,
GET_PUBLIC_KEY,
DECRYPT_ZAP_EVENT,
+ DERIVE_KEY,
}
class Permission(
@@ -219,6 +220,7 @@ class ExternalSignerLauncher(
SignerType.NIP44_DECRYPT -> "nip44_decrypt"
SignerType.GET_PUBLIC_KEY -> "get_public_key"
SignerType.DECRYPT_ZAP_EVENT -> "decrypt_zap_event"
+ SignerType.DERIVE_KEY -> "derive_key"
}
intent.putExtra("type", signerType)
intent.putExtra("pubKey", pubKey)
@@ -318,6 +320,15 @@ class ExternalSignerLauncher(
return kotlin.Result.success(null)
}
+ fun hashCodeFields(
+ str1: String,
+ onReady: (String) -> Unit,
+ ): Int {
+ var result = str1.hashCode()
+ result = 31 * result + onReady.hashCode()
+ return result
+ }
+
fun hashCodeFields(
str1: String,
str2: String,
@@ -398,4 +409,27 @@ class ExternalSignerLauncher(
},
)
}
+
+ fun deriveKey(
+ nonce: HexKey,
+ signerType: SignerType = SignerType.DERIVE_KEY,
+ onReady: (String) -> Unit,
+ ) {
+ getDataFromResolver(signerType, arrayOf(nonce)).fold(
+ onFailure = { },
+ onSuccess = {
+ if (it == null) {
+ openSignerApp(
+ nonce,
+ signerType,
+ "",
+ hashCodeFields(nonce, onReady).toString(),
+ onReady,
+ )
+ } else {
+ onReady(it)
+ }
+ },
+ )
+ }
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt
index b1e5bf32e..bf4c2164d 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip55AndroidSigner/NostrSignerExternal.kt
@@ -133,6 +133,17 @@ class NostrSignerExternal(
)
}
+ override fun deriveKey(
+ nonce: HexKey,
+ onReady: (HexKey) -> Unit,
+ ) {
+ launcher.deriveKey(
+ nonce,
+ SignerType.DERIVE_KEY,
+ onReady,
+ )
+ }
+
override fun decryptZapEvent(
event: LnZapRequestEvent,
onReady: (LnZapPrivateEvent) -> Unit,
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt
index 3e222faab..158d75557 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportEvent.kt
@@ -25,14 +25,15 @@ import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
+import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip31Alts.AltTag
+import com.vitorpamplona.quartz.nip31Alts.alt
+import com.vitorpamplona.quartz.nip56Reports.tags.DefaultReportTag
+import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAddressTag
+import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAuthorTag
+import com.vitorpamplona.quartz.nip56Reports.tags.ReportedEventTag
import com.vitorpamplona.quartz.utils.TimeUtils
-@Immutable data class ReportedKey(
- val key: String,
- val reportType: ReportEvent.ReportType,
-)
-
// NIP 56 event.
@Immutable
class ReportEvent(
@@ -43,48 +44,36 @@ class ReportEvent(
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
+ @Transient
+ private var defaultType: ReportType? = null
+
+ private fun defaultReportTypes() = tags.mapNotNull(DefaultReportTag::parse)
+
private fun defaultReportType(): ReportType {
- // Works with old and new structures for report.
- var reportType =
- tags
- .filter { it.firstOrNull() == "report" }
- .mapNotNull { it.getOrNull(1) }
- .map { ReportType.valueOf(it.uppercase()) }
- .firstOrNull()
- if (reportType == null) {
- reportType =
- tags.mapNotNull { it.getOrNull(2) }.map { ReportType.valueOf(it.uppercase()) }.firstOrNull()
- }
- if (reportType == null) {
- reportType = ReportType.SPAM
- }
+ defaultType?.let { return it }
+
+ // search for any type in any tag.
+ val reportType =
+ defaultReportTypes().firstOrNull()
+ ?: tags.firstNotNullOfOrNull {
+ ReportedAuthorTag.parse(it)?.type
+ ?: ReportedEventTag.parse(it)?.type
+ ?: ReportedAddressTag.parse(it)?.type
+ } ?: ReportType.SPAM
+
+ defaultType = reportType
return reportType
}
- fun reportedPost() =
- tags
- .filter { it.size > 1 && it[0] == "e" }
- .map {
- ReportedKey(
- it[1],
- it.getOrNull(2)?.uppercase()?.let { it1 -> ReportType.valueOf(it1) }
- ?: defaultReportType(),
- )
- }
+ fun reportedPost() = tags.mapNotNull { ReportedEventTag.parse(it, defaultReportType()) }
- fun reportedAuthor() =
- tags
- .filter { it.size > 1 && it[0] == "p" }
- .map {
- ReportedKey(
- it[1],
- it.getOrNull(2)?.uppercase()?.let { it1 -> ReportType.valueOf(it1) }
- ?: defaultReportType(),
- )
- }
+ fun reportedAddresses() = tags.mapNotNull { ReportedAddressTag.parse(it, defaultReportType()) }
+
+ fun reportedAuthor() = tags.mapNotNull { ReportedAuthorTag.parse(it, defaultReportType()) }
companion object {
const val KIND = 1984
+ const val ALT_PREFIX = "Report for "
fun create(
reportedPost: Event,
@@ -108,32 +97,27 @@ class ReportEvent(
signer.sign(createdAt, KIND, tags, content, onReady)
}
- fun create(
- reportedUser: String,
+ fun build(
+ reportedPost: Event,
type: ReportType,
- signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
- onReady: (ReportEvent) -> Unit,
- ) {
- val content = ""
+ ) = eventTemplate(KIND, "", createdAt) {
+ alt(ALT_PREFIX + type.code)
+ event(reportedPost.id, type)
+ user(reportedPost.pubKey, type)
- val reportAuthorTag = arrayOf("p", reportedUser, type.name.lowercase())
- val alt = AltTag.assemble("Report for ${type.name}")
+ if (reportedPost is AddressableEvent) {
+ address(reportedPost.address(), type)
+ }
+ }
- val tags: Array> = arrayOf(reportAuthorTag, alt)
- signer.sign(createdAt, KIND, tags, content, onReady)
+ fun build(
+ reportedUser: HexKey,
+ type: ReportType,
+ createdAt: Long = TimeUtils.now(),
+ ) = eventTemplate(KIND, "", createdAt) {
+ alt(ALT_PREFIX + type.code)
+ user(reportedUser, type)
}
}
-
- enum class ReportType {
- EXPLICIT, // Not used anymore.
- ILLEGAL,
- SPAM,
- IMPERSONATION,
- NUDITY,
- PROFANITY,
- MALWARE,
- MOD,
- OTHER,
- }
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt
new file mode 100644
index 000000000..6ff598773
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/ReportType.kt
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports
+
+import android.util.Log
+
+enum class ReportType(
+ val code: String,
+) {
+ EXPLICIT("explicit"), // Not used anymore.
+ ILLEGAL("illegal"),
+ SPAM("spam"),
+ IMPERSONATION("impersonation"),
+ NUDITY("nudity"),
+ PROFANITY("profanity"),
+ MALWARE("malware"),
+ MOD("mod"),
+ HARASSMENT("Harrassment"),
+ OTHER("other"),
+ ;
+
+ companion object {
+ fun parseOrNull(
+ code: String,
+ tag: Array,
+ ): ReportType? =
+ when (code) {
+ EXPLICIT.code -> EXPLICIT
+ ILLEGAL.code -> ILLEGAL
+ SPAM.code -> SPAM
+ IMPERSONATION.code -> IMPERSONATION
+ NUDITY.code -> NUDITY
+ PROFANITY.code -> PROFANITY
+ MALWARE.code -> MALWARE
+ MOD.code -> MOD
+ "MOD" -> MOD
+ HARASSMENT.code -> HARASSMENT
+ OTHER.code -> OTHER
+ else -> {
+ Log.w("ReportedEventTag", "Report type not supported: $code ${tag.joinToString(", ")}")
+ null
+ }
+ }
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/TagArrayBuilderExt.kt
new file mode 100644
index 000000000..b663a3d13
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/TagArrayBuilderExt.kt
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports
+
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
+import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
+import com.vitorpamplona.quartz.nip56Reports.tags.HashSha256Tag
+import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAddressTag
+import com.vitorpamplona.quartz.nip56Reports.tags.ReportedAuthorTag
+import com.vitorpamplona.quartz.nip56Reports.tags.ReportedEventTag
+import com.vitorpamplona.quartz.nip56Reports.tags.ServerTag
+
+fun TagArrayBuilder.event(
+ eventId: HexKey,
+ reportType: ReportType,
+) = addUnique(ReportedEventTag.assemble(eventId, reportType))
+
+fun TagArrayBuilder.address(
+ address: Address,
+ reportType: ReportType,
+) = addUnique(ReportedAddressTag.assemble(address, reportType))
+
+fun TagArrayBuilder.user(
+ pubkey: HexKey,
+ reportType: ReportType,
+) = addUnique(ReportedAuthorTag.assemble(pubkey, reportType))
+
+fun TagArrayBuilder.hash(
+ x: String,
+ reportType: ReportType,
+) = addUnique(HashSha256Tag.assemble(x, reportType))
+
+fun TagArrayBuilder.server(url: String) = addUnique(ServerTag.assemble(url))
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/BaseReportTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/BaseReportTag.kt
new file mode 100644
index 000000000..0ccff5b2c
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/BaseReportTag.kt
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports.tags
+
+import com.vitorpamplona.quartz.nip56Reports.ReportType
+
+interface BaseReportTag {
+ val type: ReportType?
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt
new file mode 100644
index 000000000..adc260a1b
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/DefaultReportTag.kt
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports.tags
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.nip56Reports.ReportType
+import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
+
+@Immutable
+@Deprecated("Old version had [`report`, ``] tags")
+class DefaultReportTag {
+ companion object {
+ const val TAG_NAME = "report"
+
+ @JvmStatic
+ fun parse(tag: Array): ReportType? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+
+ return ReportType.parseOrNull(tag[1], tag)
+ }
+
+ @JvmStatic
+ fun assemble(type: ReportType? = null) = arrayOfNotNull(TAG_NAME, type?.code)
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/HashSha256Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/HashSha256Tag.kt
new file mode 100644
index 000000000..172e94c13
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/HashSha256Tag.kt
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports.tags
+
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.nip56Reports.ReportType
+import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
+
+class HashSha256Tag(
+ val hash: HexKey,
+ override val type: ReportType? = null,
+) : BaseReportTag {
+ companion object {
+ const val TAG_NAME = "x"
+
+ @JvmStatic
+ fun parse(
+ tag: Array,
+ defaultReportType: ReportType? = null,
+ ): HashSha256Tag? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+
+ val type = tag.getOrNull(2)?.let { ReportType.parseOrNull(it, tag) } ?: defaultReportType
+ return HashSha256Tag(tag[1], type)
+ }
+
+ @JvmStatic
+ fun assemble(
+ hash: String,
+ type: ReportType? = null,
+ ) = arrayOfNotNull(TAG_NAME, hash, type?.code)
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAddressTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAddressTag.kt
new file mode 100644
index 000000000..8910a5fe2
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAddressTag.kt
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports.tags
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
+import com.vitorpamplona.quartz.nip56Reports.ReportType
+import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
+
+@Immutable
+class ReportedAddressTag(
+ val address: Address,
+ override val type: ReportType? = null,
+) : BaseReportTag {
+ fun toTagArray() = assemble(address, type)
+
+ companion object {
+ const val TAG_NAME = "a"
+
+ @JvmStatic
+ fun parse(
+ tag: Array,
+ defaultReportType: ReportType? = null,
+ ): ReportedAddressTag? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+
+ val address = Address.parse(tag[1])
+
+ ensure(address != null) { return null }
+
+ val type =
+ if (tag.size == 2) {
+ defaultReportType
+ } else if (tag.size == 3) {
+ ReportType.parseOrNull(tag[2], tag) ?: defaultReportType
+ } else {
+ ReportType.parseOrNull(tag[3], tag) ?: defaultReportType
+ }
+
+ return ReportedAddressTag(address, type)
+ }
+
+ @JvmStatic
+ fun assemble(
+ address: Address,
+ type: ReportType? = null,
+ ) = arrayOfNotNull(TAG_NAME, address.toValue(), type?.code)
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAuthorTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAuthorTag.kt
new file mode 100644
index 000000000..a0d155dcd
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedAuthorTag.kt
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports.tags
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.nip56Reports.ReportType
+import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
+
+@Immutable
+class ReportedAuthorTag(
+ val pubkey: HexKey,
+ override val type: ReportType? = null,
+) : BaseReportTag {
+ fun toTagArray() = assemble(pubkey, type)
+
+ companion object {
+ const val TAG_NAME = "p"
+
+ @JvmStatic
+ fun parse(
+ tag: Array,
+ defaultReportType: ReportType? = null,
+ ): ReportedAuthorTag? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+
+ val type =
+ if (tag.size == 2) {
+ defaultReportType
+ } else if (tag.size == 3) {
+ ReportType.parseOrNull(tag[2], tag) ?: defaultReportType
+ } else {
+ ReportType.parseOrNull(tag[3], tag) ?: defaultReportType
+ }
+
+ return ReportedAuthorTag(tag[1], type)
+ }
+
+ @JvmStatic
+ fun assemble(
+ pubkey: HexKey,
+ type: ReportType? = null,
+ ) = arrayOfNotNull(TAG_NAME, pubkey, type?.code)
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedEventTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedEventTag.kt
new file mode 100644
index 000000000..a939f0c7d
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ReportedEventTag.kt
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports.tags
+
+import androidx.compose.runtime.Immutable
+import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.nip56Reports.ReportType
+import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
+
+@Immutable
+class ReportedEventTag(
+ val eventId: HexKey,
+ override val type: ReportType? = null,
+) : BaseReportTag {
+ fun toTagArray() = assemble(eventId, type)
+
+ companion object {
+ const val TAG_NAME = "e"
+
+ @JvmStatic
+ fun parse(
+ tag: Array,
+ defaultReportType: ReportType? = null,
+ ): ReportedEventTag? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
+
+ val type =
+ if (tag.size == 2) {
+ defaultReportType
+ } else if (tag.size == 3) {
+ ReportType.parseOrNull(tag[2], tag) ?: defaultReportType
+ } else {
+ ReportType.parseOrNull(tag[3], tag) ?: defaultReportType
+ }
+
+ return ReportedEventTag(tag[1], type)
+ }
+
+ @JvmStatic
+ fun assemble(
+ eventId: HexKey,
+ type: ReportType? = null,
+ ) = arrayOfNotNull(TAG_NAME, eventId, type?.code)
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ServerTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ServerTag.kt
new file mode 100644
index 000000000..9d42dc08d
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip56Reports/tags/ServerTag.kt
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip56Reports.tags
+
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
+class ServerTag {
+ companion object {
+ const val TAG_NAME = "server"
+
+ @JvmStatic
+ fun parse(tag: Array): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ return tag[1]
+ }
+
+ @JvmStatic
+ fun assemble(url: String) = arrayOf(TAG_NAME, url)
+
+ @JvmStatic
+ fun assemble(urls: List) = urls.map { assemble(it) }
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/zapraiser/ZapRaiserTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/zapraiser/ZapRaiserTag.kt
index 192ade51d..66feb1396 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/zapraiser/ZapRaiserTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip57Zaps/zapraiser/ZapRaiserTag.kt
@@ -31,7 +31,7 @@ class ZapRaiserTag(
fun toTagArray() = assemble(amountInSats)
companion object {
- val TAG_NAME = "zapraiser"
+ const val TAG_NAME = "zapraiser"
@JvmStatic
fun parse(tags: Array): ZapRaiserTag? {
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/EventExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/EventExt.kt
index 11ceb0b5a..62fe0ab62 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/EventExt.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/EventExt.kt
@@ -22,6 +22,4 @@ package com.vitorpamplona.quartz.nip70ProtectedEvts
import com.vitorpamplona.quartz.nip01Core.core.Event
-const val PROTECTED_TAG = "-"
-
-fun Event.isProtected() = tags.any { (it.size > 0 && it[0] == PROTECTED_TAG) }
+fun Event.isProtected() = tags.isProtected()
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayBuilderExt.kt
new file mode 100644
index 000000000..3f820f553
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayBuilderExt.kt
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip70ProtectedEvts
+
+import com.vitorpamplona.quartz.nip01Core.core.Event
+import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
+import com.vitorpamplona.quartz.nip70ProtectedEvts.tags.ProtectedTag
+
+fun TagArrayBuilder.protect() = addUnique(ProtectedTag.assemble())
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/ProtectedTagSerializer.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayExt.kt
similarity index 86%
rename from quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/ProtectedTagSerializer.kt
rename to quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayExt.kt
index 4245aefd7..27260e8b1 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/ProtectedTagSerializer.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/TagArrayExt.kt
@@ -20,8 +20,7 @@
*/
package com.vitorpamplona.quartz.nip70ProtectedEvts
-class ProtectedTagSerializer {
- companion object {
- fun toTagArray(reason: String = "") = arrayOf("-", reason)
- }
-}
+import com.vitorpamplona.quartz.nip01Core.core.TagArray
+import com.vitorpamplona.quartz.nip70ProtectedEvts.tags.ProtectedTag
+
+fun TagArray.isProtected() = any(ProtectedTag::match)
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/tags/ProtectedTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/tags/ProtectedTag.kt
new file mode 100644
index 000000000..c9027d9dc
--- /dev/null
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip70ProtectedEvts/tags/ProtectedTag.kt
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2024 Vitor Pamplona
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+ * Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.vitorpamplona.quartz.nip70ProtectedEvts.tags
+
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
+class ProtectedTag {
+ companion object {
+ const val TAG_NAME = "-"
+
+ @JvmStatic
+ fun match(tag: Array): Boolean {
+ ensure(tag.has(0)) { return false }
+ ensure(tag[0] == TAG_NAME) { return false }
+ return true
+ }
+
+ @JvmStatic
+ fun parse(tag: Array): Boolean? {
+ ensure(tag.has(0)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ return true
+ }
+
+ @JvmStatic
+ fun assemble() = arrayOf(TAG_NAME)
+ }
+}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/DurationTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/DurationTag.kt
index 4e4fed010..fc378cc8a 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/DurationTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/DurationTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip71Video.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class DurationTag {
companion object {
const val TAG_NAME = "duration"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Int? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toIntOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/SegmentTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/SegmentTag.kt
index 6feb0fea0..2d4a09bd4 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/SegmentTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/SegmentTag.kt
@@ -20,7 +20,9 @@
*/
package com.vitorpamplona.quartz.nip71Video.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
class SegmentTag(
val start: String, // HH:MM:SS.sss
@@ -32,11 +34,14 @@ class SegmentTag(
companion object {
const val TAG_NAME = "segment"
- const val TAG_SIZE = 4
@JvmStatic
fun parse(tag: Array): SegmentTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(3)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ ensure(tag[2].isNotEmpty()) { return null }
+ ensure(tag[3].isNotEmpty()) { return null }
return SegmentTag(tag[1], tag[2], tag[3], tag.getOrNull(4))
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/TextTrackTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/TextTrackTag.kt
index 1f0680f46..a624e0c09 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/TextTrackTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/tags/TextTrackTag.kt
@@ -21,8 +21,10 @@
package com.vitorpamplona.quartz.nip71Video.tags
import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
+import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
data class TextTrackTag(
@@ -38,11 +40,12 @@ data class TextTrackTag(
companion object {
const val TAG_NAME = "text-track"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): TextTrackTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return TextTrackTag(tag[1], tag.getOrNull(2))
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt
index 84a2edb9d..5e76b7ed9 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class DescriptionTag {
companion object {
const val TAG_NAME = "description"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt
index 9bae1d46a..2f3a7dbe3 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt
@@ -20,8 +20,10 @@
*/
package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
class ImageTag(
val imageUrl: String,
@@ -29,12 +31,12 @@ class ImageTag(
) {
companion object {
const val TAG_NAME = "image"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): ImageTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
- if (tag[1].isEmpty()) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
val dims = tag.getOrNull(2)?.let { DimensionTag.parse(it) }
return ImageTag(tag[1], dims)
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt
index 08d901cc2..ddd2b46a2 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt
@@ -23,9 +23,10 @@ package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
-import com.vitorpamplona.quartz.nip01Core.core.isNotName
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
@Immutable
data class ModeratorTag(
@@ -37,19 +38,20 @@ data class ModeratorTag(
companion object {
const val TAG_NAME = "p"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Tag): ModeratorTag? {
- if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return ModeratorTag(tag[1], tag.getOrNull(2), tag.getOrNull(3))
}
@JvmStatic
fun parseKey(tag: Tag): String? {
- if (tag.isNotName(TAG_NAME, TAG_SIZE)) return null
- if (tag[1].length != 64) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].length == 64) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt
index 0c66da66d..5f23b0f82 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class NameTag {
companion object {
const val TAG_NAME = "name"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt
index a70f2f7d9..6e5c4dd14 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt
@@ -20,7 +20,9 @@
*/
package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
class RelayTag(
val url: String,
@@ -30,11 +32,12 @@ class RelayTag(
companion object {
const val TAG_NAME = "relay"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): RelayTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return RelayTag(tag[1], tag.getOrNull(2))
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt
index 467216f8f..3908eca08 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip72ModCommunities.definition.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class RulesTag {
companion object {
const val TAG_NAME = "rules"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt
index e5c24cae3..177c985ab 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip75ZapGoals.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class AmountTag {
companion object {
const val TAG_NAME = "amount"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt
index 2fbd2a885..2af7ec8d6 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip75ZapGoals.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class ClosedAtTag {
companion object {
const val TAG_NAME = "closed_at"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Long? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toLongOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt
index bb5c0811a..9f49ce761 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt
@@ -20,16 +20,20 @@
*/
package com.vitorpamplona.quartz.nip75ZapGoals.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class RelayListTag(
val relayUrls: List,
) {
companion object {
const val TAG_NAME = "relays"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): RelayListTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
val relays =
tag.mapIndexedNotNull { index, s ->
if (index == 0) null else s
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt
index 6e46a32e7..80daa368f 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip84Highlights.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class ContextTag {
companion object {
const val TAG_NAME = "context"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt
index 94f5f798d..8c18bf480 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt
@@ -21,6 +21,7 @@
package com.vitorpamplona.quartz.nip89AppHandlers.definition.tags
import com.vitorpamplona.quartz.nip01Core.core.Tag
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip89AppHandlers.PlatformType
import com.vitorpamplona.quartz.utils.arrayOfNotNull
@@ -32,14 +33,10 @@ class PlatformLinkTag(
fun toTagArray() = assemble(platform, uri, entityType)
companion object {
- const val TAG_SIZE = 3
-
@JvmStatic
fun match(tag: Tag): Boolean =
- if (tag.size >= TAG_SIZE) {
- tag[0] == PlatformType.IOS.code ||
- tag[0] == PlatformType.WEB.code ||
- tag[0] == PlatformType.ANDROID.code
+ if (tag.has(2)) {
+ tag[0] == PlatformType.IOS.code || tag[0] == PlatformType.WEB.code || tag[0] == PlatformType.ANDROID.code
} else {
false
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt
index 0b3db29b5..4b98181e7 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt
@@ -43,7 +43,7 @@ class AppRecommendationEvent(
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
fun recommendations() = tags.mapNotNull(RecommendationTag::parse)
- fun recommendationAddresses() = tags.mapNotNull(RecommendationTag::parseAddress)
+ fun recommendationAddresses() = tags.mapNotNull(RecommendationTag::parseAddressId)
companion object {
const val KIND = 31989
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt
index 045510e69..22c8fea76 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt
@@ -23,8 +23,6 @@ package com.vitorpamplona.quartz.nip89AppHandlers.recommendation.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Tag
import com.vitorpamplona.quartz.nip01Core.core.has
-import com.vitorpamplona.quartz.nip01Core.core.match
-import com.vitorpamplona.quartz.nip01Core.core.valueIfMatches
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.ensure
@@ -39,10 +37,9 @@ class RecommendationTag(
companion object {
const val TAG_NAME = "a"
- const val TAG_SIZE = 2
@JvmStatic
- fun match(tag: Tag) = tag.match(TAG_NAME, TAG_SIZE)
+ fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array): RecommendationTag? {
@@ -54,7 +51,12 @@ class RecommendationTag(
}
@JvmStatic
- fun parseAddress(tag: Array) = tag.valueIfMatches(TAG_NAME, TAG_SIZE)
+ fun parseAddressId(tag: Array): String? {
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
+ return tag[1]
+ }
@JvmStatic
fun assemble(
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt
index 7f8c1c8d1..1c7d89153 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt
@@ -20,6 +20,9 @@
*/
package com.vitorpamplona.quartz.nip92IMeta
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class IMetaTag(
val url: String,
val properties: Map>,
@@ -38,10 +41,11 @@ class IMetaTag(
companion object {
const val TAG_NAME = "imeta"
const val ANCHOR_PROPERTY = "url"
- const val TAG_SIZE = 2
fun parse(tag: Array): IMetaTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
val allTags = parseIMeta(tag)
val url = allTags.get(ANCHOR_PROPERTY)?.firstOrNull()
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt
index 329314d84..57c646976 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class BlurhashTag {
companion object {
const val TAG_NAME = "blurhash"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt
index dcfd011ca..515b7c916 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt
@@ -20,6 +20,9 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class DimensionTag(
val width: Int,
val height: Int,
@@ -34,11 +37,12 @@ class DimensionTag(
companion object {
const val TAG_NAME = "dim"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): DimensionTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return parse(tag[1])
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt
index 4eca6fd06..b00bfc43c 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class FallbackTag {
companion object {
const val TAG_NAME = "fallback"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt
index 9880e9162..adf394c3a 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class HashSha256Tag {
companion object {
const val TAG_NAME = "x"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME || tag[1].isEmpty()) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt
index 7aa7274a2..f8423bd3e 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt
@@ -21,7 +21,9 @@
package com.vitorpamplona.quartz.nip94FileMetadata.tags
import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
class ImageTag(
val imageUrl: String,
@@ -29,11 +31,12 @@ class ImageTag(
) {
companion object {
const val TAG_NAME = "image"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): ImageTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return ImageTag(tag[1], tag.getOrNull(2))
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt
index 9bd916cd7..d608a6f64 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class MagnetTag {
companion object {
const val TAG_NAME = "magnet"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt
index 416b3f0a1..c0555f089 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt
@@ -20,19 +20,23 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class MimeTypeTag {
companion object {
const val TAG_NAME = "m"
- const val TAG_SIZE = 2
fun isIn(
tag: Array,
mimeTypes: Set,
- ) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME && tag[1] in mimeTypes
+ ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in mimeTypes
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt
index 6cc31f954..f17d01e75 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class OriginalHashTag {
companion object {
const val TAG_NAME = "ox"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt
index ca4703c05..18c6a9315 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt
@@ -20,17 +20,21 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class ServiceTag {
companion object {
const val TAG_NAME = "service"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
+ fun isTag(tag: Array) = tag.has(0) && tag[0] == TAG_NAME
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt
index 4548ea5f5..645a77d1f 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class SizeTag {
companion object {
const val TAG_NAME = "size"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): Int? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1].toIntOrNull()
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt
index b854ab7a6..b0a405fca 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt
@@ -20,17 +20,21 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class SummaryTag {
companion object {
const val TAG_NAME = "summary"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
+ fun isTag(tag: Array) = tag.has(1) && tag[0] == TAG_NAME
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt
index e11faa1be..52e53e202 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt
@@ -21,7 +21,9 @@
package com.vitorpamplona.quartz.nip94FileMetadata.tags
import com.vitorpamplona.quartz.nip01Core.core.HexKey
+import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
+import com.vitorpamplona.quartz.utils.ensure
class ThumbTag(
val imageUrl: String,
@@ -29,11 +31,12 @@ class ThumbTag(
) {
companion object {
const val TAG_NAME = "thumb"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): ThumbTag? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return ThumbTag(tag[1], tag.getOrNull(2))
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt
index 93b87b03e..4ea1a7d4b 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt
@@ -20,14 +20,18 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class TorrentInfoHash {
companion object {
const val TAG_NAME = "i"
- const val TAG_SIZE = 2
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt
index 4be128be8..05bcd0a91 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt
@@ -20,17 +20,21 @@
*/
package com.vitorpamplona.quartz.nip94FileMetadata.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class UrlTag {
companion object {
const val TAG_NAME = "url"
- const val TAG_SIZE = 2
@JvmStatic
- fun isTag(tag: Array) = tag.size >= TAG_SIZE && tag[0] == TAG_NAME
+ fun isTag(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic
fun parse(tag: Array): String? {
- if (tag.size < TAG_SIZE || tag[0] != TAG_NAME) return null
+ ensure(tag.has(1)) { return null }
+ ensure(tag[0] == TAG_NAME) { return null }
+ ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt
index 4e326bb9d..2b926fb6e 100644
--- a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt
+++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt
@@ -20,16 +20,20 @@
*/
package com.vitorpamplona.quartz.nip96FileStorage.config.tags
+import com.vitorpamplona.quartz.nip01Core.core.has
+import com.vitorpamplona.quartz.utils.ensure
+
class ServerTag {
companion object {
const val TAG_NAME = "server"
- const val TAG_SIZE = 2
fun isTag(tag: Array