Final touches on the Attestation cards
This commit is contained in:
@@ -784,11 +784,11 @@ private fun RenderNoteRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is AttestorRecommendationEvent -> {
|
is AttestorRecommendationEvent -> {
|
||||||
RenderAttestorRecommendation(baseNote, backgroundColor, accountViewModel, nav)
|
RenderAttestorRecommendation(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AttestorProficiencyEvent -> {
|
is AttestorProficiencyEvent -> {
|
||||||
RenderAttestorProficiency(baseNote, backgroundColor, accountViewModel, nav)
|
RenderAttestorProficiency(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AudioTrackEvent -> {
|
is AudioTrackEvent -> {
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -53,13 +55,28 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.KindChip
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus
|
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus
|
||||||
@@ -71,16 +88,75 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun RenderAttestationPreview() {
|
||||||
|
val event =
|
||||||
|
AttestationEvent(
|
||||||
|
id = "f8e05e4fa964d9dfbeb339ae0450d0b3424aaf8f6083d95f38c780335c3dbd56",
|
||||||
|
pubKey = "c4f5e7a75a8ce3683d529cff06368439c529e5243c6b125ba68789198856cac7",
|
||||||
|
createdAt = 1773941524,
|
||||||
|
content = "This is Frank's new npub.",
|
||||||
|
sig = "61dde5dc5738bfa637aa04451eec63d45f649902a0772abbaf55711aad5b7ce376bc7712c937f9ffc504655c4fd7e39f78139dc3f1a90953d150b43f3e2428fb",
|
||||||
|
tags =
|
||||||
|
arrayOf(
|
||||||
|
arrayOf("d", "af5aa898:fe108febb997:1773941524"),
|
||||||
|
arrayOf("e", "fe108febb99796c4091775e00aa1fc3ffc489ad22fdf1f8c559b2472815c09c7"),
|
||||||
|
arrayOf("s", "verified"),
|
||||||
|
arrayOf("v", "valid"),
|
||||||
|
arrayOf("client", "attestr.xyz"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
LocalCache.justConsume(event, null, true)
|
||||||
|
val note = LocalCache.getOrCreateNote(event.id)
|
||||||
|
|
||||||
|
ThemeComparisonColumn(
|
||||||
|
toPreview = {
|
||||||
|
RenderAttestation(
|
||||||
|
baseNote = note,
|
||||||
|
quotesLeft = 3,
|
||||||
|
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
||||||
|
accountViewModel = mockAccountViewModel(),
|
||||||
|
nav = EmptyNav(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderAttestation(
|
fun RenderAttestation(
|
||||||
note: Note,
|
baseNote: Note,
|
||||||
quotesLeft: Int,
|
quotesLeft: Int,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? AttestationEvent ?: return
|
val noteState by baseNote
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
val noteEvent = noteState.note.event as? AttestationEvent ?: return
|
||||||
|
|
||||||
|
RenderAttestation(
|
||||||
|
baseNote,
|
||||||
|
noteEvent,
|
||||||
|
quotesLeft,
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderAttestation(
|
||||||
|
note: Note,
|
||||||
|
noteEvent: AttestationEvent,
|
||||||
|
quotesLeft: Int,
|
||||||
|
backgroundColor: MutableState<Color>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
val validity = remember(noteEvent) { noteEvent.validity() }
|
val validity = remember(noteEvent) { noteEvent.validity() }
|
||||||
val status = remember(noteEvent) { noteEvent.status() }
|
val status = remember(noteEvent) { noteEvent.status() }
|
||||||
val validFrom = remember(noteEvent) { noteEvent.validFrom() }
|
val validFrom = remember(noteEvent) { noteEvent.validFrom() }
|
||||||
@@ -91,6 +167,10 @@ fun RenderAttestation(
|
|||||||
val statusIcon = remember(status, validity) { attestationIcon(status, validity) }
|
val statusIcon = remember(status, validity) { attestationIcon(status, validity) }
|
||||||
val statusLabel = attestationStatusLabel(status, validity)
|
val statusLabel = attestationStatusLabel(status, validity)
|
||||||
|
|
||||||
|
val aboutAddress = remember(noteEvent) { noteEvent.assertionAddress() }
|
||||||
|
val aboutEvent = remember(noteEvent) { noteEvent.assertionEventId() }
|
||||||
|
val aboutPubkey = remember(noteEvent) { noteEvent.assertionPubkey() }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
@@ -119,7 +199,7 @@ fun RenderAttestation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (validFrom != null || validTo != null) {
|
if (validFrom != null || validTo != null) {
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
validFrom?.let {
|
validFrom?.let {
|
||||||
Text(
|
Text(
|
||||||
@@ -138,32 +218,62 @@ fun RenderAttestation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content?.let {
|
if (quotesLeft > 0) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
if (aboutAddress != null) {
|
||||||
Text(
|
LoadAddressableNote(aboutAddress, accountViewModel) {
|
||||||
text = it,
|
if (it != null) {
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
NoteCompose(
|
||||||
)
|
baseNote = it,
|
||||||
|
modifier = MaterialTheme.colorScheme.replyModifier,
|
||||||
|
isQuotedNote = true,
|
||||||
|
unPackReply = ReplyRenderType.NONE,
|
||||||
|
makeItShort = true,
|
||||||
|
quotesLeft = quotesLeft - 1,
|
||||||
|
parentBackgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (aboutEvent != null) {
|
||||||
|
LoadNote(aboutEvent, accountViewModel) {
|
||||||
|
if (it != null) {
|
||||||
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
NoteCompose(
|
||||||
|
baseNote = it,
|
||||||
|
modifier = MaterialTheme.colorScheme.replyModifier,
|
||||||
|
isQuotedNote = true,
|
||||||
|
unPackReply = ReplyRenderType.NONE,
|
||||||
|
makeItShort = true,
|
||||||
|
quotesLeft = quotesLeft - 1,
|
||||||
|
parentBackgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (aboutPubkey != null) {
|
||||||
|
LoadUser(aboutPubkey, accountViewModel) {
|
||||||
|
if (it != null) {
|
||||||
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
UserCompose(it, accountViewModel = accountViewModel, nav = nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (quotesLeft > 0) {
|
content?.let {
|
||||||
note.replyTo?.firstOrNull()?.let {
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
TranslatableRichTextViewer(
|
||||||
Text(
|
content = it,
|
||||||
text = stringRes(R.string.attestation_attests_to),
|
canPreview = true,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
quotesLeft = quotesLeft,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
tags = remember(note) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList },
|
||||||
NoteCompose(
|
backgroundColor = backgroundColor,
|
||||||
baseNote = it,
|
id = note.idHex,
|
||||||
modifier = MaterialTheme.colorScheme.replyModifier,
|
callbackUri = note.toNostrUri(),
|
||||||
isQuotedNote = true,
|
|
||||||
unPackReply = ReplyRenderType.NONE,
|
|
||||||
makeItShort = true,
|
|
||||||
quotesLeft = quotesLeft - 1,
|
|
||||||
parentBackgroundColor = backgroundColor,
|
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
@@ -179,9 +289,18 @@ fun RenderAttestationRequest(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? AttestationRequestEvent ?: return
|
val noteState by note
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
val noteEvent = noteState.note.event as? AttestationRequestEvent ?: return
|
||||||
|
|
||||||
val content = remember(noteEvent) { noteEvent.content.ifBlank { null } }
|
val content = remember(noteEvent) { noteEvent.content.ifBlank { null } }
|
||||||
|
|
||||||
|
val aboutAddress = remember(noteEvent) { noteEvent.assertionAddress() }
|
||||||
|
val aboutEvent = remember(noteEvent) { noteEvent.assertionEventId() }
|
||||||
|
val aboutPubkey = remember(noteEvent) { noteEvent.assertionPubkey() }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
@@ -213,29 +332,79 @@ fun RenderAttestationRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
content?.let {
|
content?.let {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
Text(
|
TranslatableRichTextViewer(
|
||||||
text = it,
|
content = it,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
canPreview = true,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
quotesLeft = quotesLeft,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
tags = remember(note) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList },
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
id = note.idHex,
|
||||||
|
callbackUri = note.toNostrUri(),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quotesLeft > 0) {
|
if (quotesLeft > 0) {
|
||||||
note.replyTo?.firstOrNull()?.let {
|
if (aboutAddress != null) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
LoadAddressableNote(aboutAddress, accountViewModel) {
|
||||||
NoteCompose(
|
if (it != null) {
|
||||||
baseNote = it,
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
modifier = MaterialTheme.colorScheme.replyModifier,
|
Text(
|
||||||
isQuotedNote = true,
|
text = stringRes(R.string.attestation_requests_attestation_to),
|
||||||
unPackReply = ReplyRenderType.NONE,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
makeItShort = true,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
quotesLeft = quotesLeft - 1,
|
)
|
||||||
parentBackgroundColor = backgroundColor,
|
NoteCompose(
|
||||||
accountViewModel = accountViewModel,
|
baseNote = it,
|
||||||
nav = nav,
|
modifier = MaterialTheme.colorScheme.replyModifier,
|
||||||
)
|
isQuotedNote = true,
|
||||||
|
unPackReply = ReplyRenderType.NONE,
|
||||||
|
makeItShort = true,
|
||||||
|
quotesLeft = quotesLeft - 1,
|
||||||
|
parentBackgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (aboutEvent != null) {
|
||||||
|
LoadNote(aboutEvent, accountViewModel) {
|
||||||
|
if (it != null) {
|
||||||
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.attestation_requests_attestation_to),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
NoteCompose(
|
||||||
|
baseNote = it,
|
||||||
|
modifier = MaterialTheme.colorScheme.replyModifier,
|
||||||
|
isQuotedNote = true,
|
||||||
|
unPackReply = ReplyRenderType.NONE,
|
||||||
|
makeItShort = true,
|
||||||
|
quotesLeft = quotesLeft - 1,
|
||||||
|
parentBackgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (aboutPubkey != null) {
|
||||||
|
LoadUser(aboutPubkey, accountViewModel) {
|
||||||
|
if (it != null) {
|
||||||
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.attestation_requests_attestation_to),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
UserCompose(it, accountViewModel = accountViewModel, nav = nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,13 +413,20 @@ fun RenderAttestationRequest(
|
|||||||
@Composable
|
@Composable
|
||||||
fun RenderAttestorRecommendation(
|
fun RenderAttestorRecommendation(
|
||||||
note: Note,
|
note: Note,
|
||||||
|
quotesLeft: Int,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? AttestorRecommendationEvent ?: return
|
val noteState by note
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
val noteEvent = noteState.note.event as? AttestorRecommendationEvent ?: return
|
||||||
|
|
||||||
val kinds = remember(noteEvent) { noteEvent.kinds() }
|
val kinds = remember(noteEvent) { noteEvent.kinds() }
|
||||||
val description = remember(noteEvent) { noteEvent.description() }
|
val description = remember(noteEvent) { noteEvent.description() }
|
||||||
|
val aboutPubKey = remember(noteEvent) { noteEvent.dTag() }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
@@ -282,25 +458,45 @@ fun RenderAttestorRecommendation(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoadUser(aboutPubKey, accountViewModel) {
|
||||||
|
if (it != null) {
|
||||||
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
UserCompose(it, accountViewModel = accountViewModel, nav = nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (kinds.isNotEmpty()) {
|
if (kinds.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
Text(
|
FlowRow(
|
||||||
text =
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
stringRes(
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
R.string.attestor_recommendation_for_kinds,
|
) {
|
||||||
kinds.joinToString(", "),
|
Text(
|
||||||
),
|
text = stringRes(R.string.attestor_recommendation_for_kinds),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Kinds
|
||||||
|
kinds.forEach { kind ->
|
||||||
|
KindChip(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
description?.let {
|
description?.let {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
Text(
|
TranslatableRichTextViewer(
|
||||||
text = it,
|
content = it,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
canPreview = true,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
quotesLeft = quotesLeft,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
tags = remember(note) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList },
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
id = note.idHex,
|
||||||
|
callbackUri = note.toNostrUri(),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,11 +506,17 @@ fun RenderAttestorRecommendation(
|
|||||||
@Composable
|
@Composable
|
||||||
fun RenderAttestorProficiency(
|
fun RenderAttestorProficiency(
|
||||||
note: Note,
|
note: Note,
|
||||||
|
quotesLeft: Int,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? AttestorProficiencyEvent ?: return
|
val noteState by note
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
val noteEvent = noteState.note.event as? AttestorProficiencyEvent ?: return
|
||||||
|
|
||||||
val kinds = remember(noteEvent) { noteEvent.kinds() }
|
val kinds = remember(noteEvent) { noteEvent.kinds() }
|
||||||
val description = remember(noteEvent) { noteEvent.description() }
|
val description = remember(noteEvent) { noteEvent.description() }
|
||||||
|
|
||||||
@@ -368,10 +570,17 @@ fun RenderAttestorProficiency(
|
|||||||
|
|
||||||
description?.let {
|
description?.let {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
TranslatableRichTextViewer(
|
||||||
text = it,
|
content = it,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
canPreview = true,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
quotesLeft = quotesLeft,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
tags = remember(note) { note.event?.tags?.toImmutableListOfLists() ?: EmptyTagList },
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
id = note.idHex,
|
||||||
|
callbackUri = note.toNostrUri(),
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -669,7 +669,7 @@ val zaps = setOf(9734, 9735, 9041, 17375, 23194, 23195)
|
|||||||
val reports = setOf(ReportEvent.KIND, MuteListEvent.KIND, DeletionEvent.KIND, RequestToVanishEvent.KIND)
|
val reports = setOf(ReportEvent.KIND, MuteListEvent.KIND, DeletionEvent.KIND, RequestToVanishEvent.KIND)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun KindChip(kind: Int) {
|
fun KindChip(kind: Int) {
|
||||||
val nameResId = kindDisplayName(kind)
|
val nameResId = kindDisplayName(kind)
|
||||||
val name = if (nameResId != -1) stringResource(nameResId) else "k$kind"
|
val name = if (nameResId != -1) stringResource(nameResId) else "k$kind"
|
||||||
val (bg, fg) =
|
val (bg, fg) =
|
||||||
|
|||||||
+2
-2
@@ -633,9 +633,9 @@ private fun FullBleedNoteCompose(
|
|||||||
} else if (noteEvent is AttestationRequestEvent) {
|
} else if (noteEvent is AttestationRequestEvent) {
|
||||||
RenderAttestationRequest(baseNote, 3, backgroundColor, accountViewModel, nav)
|
RenderAttestationRequest(baseNote, 3, backgroundColor, accountViewModel, nav)
|
||||||
} else if (noteEvent is AttestorRecommendationEvent) {
|
} else if (noteEvent is AttestorRecommendationEvent) {
|
||||||
RenderAttestorRecommendation(baseNote, backgroundColor, accountViewModel, nav)
|
RenderAttestorRecommendation(baseNote, 3, backgroundColor, accountViewModel, nav)
|
||||||
} else if (noteEvent is AttestorProficiencyEvent) {
|
} else if (noteEvent is AttestorProficiencyEvent) {
|
||||||
RenderAttestorProficiency(baseNote, backgroundColor, accountViewModel, nav)
|
RenderAttestorProficiency(baseNote, 3, backgroundColor, accountViewModel, nav)
|
||||||
} else if (noteEvent is AdvertisedRelayListEvent) {
|
} else if (noteEvent is AdvertisedRelayListEvent) {
|
||||||
DisplayNIP65RelayList(baseNote, backgroundColor, accountViewModel, nav)
|
DisplayNIP65RelayList(baseNote, backgroundColor, accountViewModel, nav)
|
||||||
} else if (noteEvent is LnZapEvent) {
|
} else if (noteEvent is LnZapEvent) {
|
||||||
|
|||||||
@@ -1907,8 +1907,9 @@
|
|||||||
<string name="attestation_request">Attestation Request</string>
|
<string name="attestation_request">Attestation Request</string>
|
||||||
<string name="attestation_request_description">Requesting attestation for an event</string>
|
<string name="attestation_request_description">Requesting attestation for an event</string>
|
||||||
<string name="attestor_recommendation">Attestor Recommendation</string>
|
<string name="attestor_recommendation">Attestor Recommendation</string>
|
||||||
<string name="attestor_recommendation_for_kinds">Recommended for kinds: %1$s</string>
|
<string name="attestor_recommendation_for_kinds">Recommended for kinds: </string>
|
||||||
<string name="attestor_proficiency">Attestor Proficiency</string>
|
<string name="attestor_proficiency">Attestor Proficiency</string>
|
||||||
<string name="attestor_proficiency_for_kinds">Proficient in verifying kinds: %1$s</string>
|
<string name="attestor_proficiency_for_kinds">Proficient in verifying kinds: %1$s</string>
|
||||||
<string name="attestation_attests_to">Attests to</string>
|
<string name="attestation_attests_to">Attests to</string>
|
||||||
|
<string name="attestation_requests_attestation_to">Requests attestation to</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+97
-3
@@ -23,13 +23,26 @@ package com.vitorpamplona.quartz.experimental.attestations.attestation
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus
|
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus
|
||||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Validity
|
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Validity
|
||||||
|
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import kotlinx.serialization.json.JsonNull.content
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class AttestationEvent(
|
class AttestationEvent(
|
||||||
@@ -39,7 +52,22 @@ class AttestationEvent(
|
|||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: HexKey,
|
sig: HexKey,
|
||||||
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||||
|
EventHintProvider,
|
||||||
|
AddressHintProvider,
|
||||||
|
PubKeyHintProvider {
|
||||||
|
override fun eventHints(): List<EventIdHint> = tags.mapNotNull(ETag::parseAsHint)
|
||||||
|
|
||||||
|
override fun linkedEventIds(): List<HexKey> = tags.mapNotNull(ETag::parseId)
|
||||||
|
|
||||||
|
override fun addressHints(): List<AddressHint> = tags.mapNotNull(ATag::parseAsHint)
|
||||||
|
|
||||||
|
override fun linkedAddressIds(): List<String> = tags.mapNotNull(ATag::parseAddressId)
|
||||||
|
|
||||||
|
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
|
||||||
|
|
||||||
|
override fun linkedPubKeys() = tags.mapNotNull(PTag::parseKey)
|
||||||
|
|
||||||
fun validity() = tags.validity()
|
fun validity() = tags.validity()
|
||||||
|
|
||||||
fun status() = tags.status()
|
fun status() = tags.status()
|
||||||
@@ -50,25 +78,91 @@ class AttestationEvent(
|
|||||||
|
|
||||||
fun request() = tags.request()
|
fun request() = tags.request()
|
||||||
|
|
||||||
|
fun requestId() = tags.requestId()
|
||||||
|
|
||||||
|
fun requestAddress() = tags.requestAddress()
|
||||||
|
|
||||||
fun isRevoked() = status() == AttestationStatus.REVOKED
|
fun isRevoked() = status() == AttestationStatus.REVOKED
|
||||||
|
|
||||||
|
fun assertionAddrId() = tags.firstNotNullOfOrNull(ATag::parseAddressId)
|
||||||
|
|
||||||
|
fun assertionAddress() = tags.firstNotNullOfOrNull(ATag::parseAddress)
|
||||||
|
|
||||||
|
fun assertionATag() = tags.firstNotNullOfOrNull(ATag::parse)
|
||||||
|
|
||||||
|
fun assertionEventId() = tags.firstNotNullOfOrNull(ETag::parseId)
|
||||||
|
|
||||||
|
fun assertionETag() = tags.firstNotNullOfOrNull(ETag::parse)
|
||||||
|
|
||||||
|
fun assertionPubkey() = tags.firstNotNullOfOrNull(PTag::parseKey)
|
||||||
|
|
||||||
|
fun assertionPTag() = tags.firstNotNullOfOrNull(PTag::parse)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = 31871
|
const val KIND = 31871
|
||||||
const val ALT_DESCRIPTION = "Attestation"
|
const val ALT_DESCRIPTION = "Attestation"
|
||||||
|
|
||||||
fun build(
|
fun buildEvent(
|
||||||
dTagId: String,
|
dTagId: String,
|
||||||
|
about: EventHintBundle<Event>,
|
||||||
content: String = "",
|
content: String = "",
|
||||||
validity: Validity? = null,
|
validity: Validity? = null,
|
||||||
status: AttestationStatus? = null,
|
status: AttestationStatus? = null,
|
||||||
validFrom: Long? = null,
|
validFrom: Long? = null,
|
||||||
validTo: Long? = null,
|
validTo: Long? = null,
|
||||||
requestAddress: String? = null,
|
requestAddress: EventHintBundle<AttestationRequestEvent>? = null,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
initializer: TagArrayBuilder<AttestationEvent>.() -> Unit = {},
|
initializer: TagArrayBuilder<AttestationEvent>.() -> Unit = {},
|
||||||
) = eventTemplate(KIND, content, createdAt) {
|
) = eventTemplate(KIND, content, createdAt) {
|
||||||
alt(ALT_DESCRIPTION)
|
alt(ALT_DESCRIPTION)
|
||||||
dTag(dTagId)
|
dTag(dTagId)
|
||||||
|
about(about)
|
||||||
|
validity?.let { validity(it) }
|
||||||
|
status?.let { status(it) }
|
||||||
|
validFrom?.let { validFrom(it) }
|
||||||
|
validTo?.let { validTo(it) }
|
||||||
|
requestAddress?.let { request(it) }
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildReplaceable(
|
||||||
|
dTagId: String,
|
||||||
|
about: EventHintBundle<BaseReplaceableEvent>,
|
||||||
|
content: String = "",
|
||||||
|
validity: Validity? = null,
|
||||||
|
status: AttestationStatus? = null,
|
||||||
|
validFrom: Long? = null,
|
||||||
|
validTo: Long? = null,
|
||||||
|
requestAddress: EventHintBundle<AttestationRequestEvent>? = null,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<AttestationEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, content, createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
dTag(dTagId)
|
||||||
|
aboutReplaceable(about)
|
||||||
|
validity?.let { validity(it) }
|
||||||
|
status?.let { status(it) }
|
||||||
|
validFrom?.let { validFrom(it) }
|
||||||
|
validTo?.let { validTo(it) }
|
||||||
|
requestAddress?.let { request(it) }
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildAddress(
|
||||||
|
dTagId: String,
|
||||||
|
about: EventHintBundle<BaseAddressableEvent>,
|
||||||
|
content: String = "",
|
||||||
|
validity: Validity? = null,
|
||||||
|
status: AttestationStatus? = null,
|
||||||
|
validFrom: Long? = null,
|
||||||
|
validTo: Long? = null,
|
||||||
|
requestAddress: EventHintBundle<AttestationRequestEvent>? = null,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<AttestationEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, content, createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
dTag(dTagId)
|
||||||
|
aboutAddressable(about)
|
||||||
validity?.let { validity(it) }
|
validity?.let { validity(it) }
|
||||||
status?.let { status(it) }
|
status?.let { status(it) }
|
||||||
validFrom?.let { validFrom(it) }
|
validFrom?.let { validFrom(it) }
|
||||||
|
|||||||
+14
-1
@@ -27,7 +27,14 @@ import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Valid
|
|||||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidToTag
|
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidToTag
|
||||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Validity
|
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.Validity
|
||||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidityTag
|
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.ValidityTag
|
||||||
|
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||||
|
|
||||||
fun TagArrayBuilder<AttestationEvent>.validity(validity: Validity) = addUnique(ValidityTag.assemble(validity))
|
fun TagArrayBuilder<AttestationEvent>.validity(validity: Validity) = addUnique(ValidityTag.assemble(validity))
|
||||||
|
|
||||||
@@ -37,4 +44,10 @@ fun TagArrayBuilder<AttestationEvent>.validFrom(timestamp: Long) = addUnique(Val
|
|||||||
|
|
||||||
fun TagArrayBuilder<AttestationEvent>.validTo(timestamp: Long) = addUnique(ValidToTag.assemble(timestamp))
|
fun TagArrayBuilder<AttestationEvent>.validTo(timestamp: Long) = addUnique(ValidToTag.assemble(timestamp))
|
||||||
|
|
||||||
fun TagArrayBuilder<AttestationEvent>.request(requestAddress: String) = addUnique(RequestTag.assemble(requestAddress))
|
fun TagArrayBuilder<AttestationEvent>.request(request: EventHintBundle<AttestationRequestEvent>) = addUnique(RequestTag.assemble(request.event.address(), request.relay))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<AttestationEvent>.aboutAddressable(request: EventHintBundle<BaseAddressableEvent>) = addUnique(ATag.assemble(request.event.address(), request.relay))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<AttestationEvent>.aboutReplaceable(request: EventHintBundle<BaseReplaceableEvent>) = addUnique(ATag.assemble(request.event.address(), request.relay))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<AttestationEvent>.about(request: EventHintBundle<Event>) = addUnique(ETag.assemble(request.event.id, request.relay, request.event.pubKey))
|
||||||
|
|||||||
+4
@@ -36,3 +36,7 @@ fun TagArray.validFrom() = firstNotNullOfOrNull(ValidFromTag::parse)
|
|||||||
fun TagArray.validTo() = firstNotNullOfOrNull(ValidToTag::parse)
|
fun TagArray.validTo() = firstNotNullOfOrNull(ValidToTag::parse)
|
||||||
|
|
||||||
fun TagArray.request() = firstNotNullOfOrNull(RequestTag::parse)
|
fun TagArray.request() = firstNotNullOfOrNull(RequestTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.requestId() = firstNotNullOfOrNull(RequestTag::parseAddressId)
|
||||||
|
|
||||||
|
fun TagArray.requestAddress() = firstNotNullOfOrNull(RequestTag::parseAddress)
|
||||||
|
|||||||
+90
-4
@@ -20,22 +20,108 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.experimental.attestations.attestation.tags
|
package com.vitorpamplona.quartz.experimental.attestations.attestation.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.has
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||||
|
import com.vitorpamplona.quartz.nip72ModCommunities.approval.tags.ApprovedAddressTag
|
||||||
|
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.arrayOfNotNull
|
||||||
import com.vitorpamplona.quartz.utils.ensure
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
class RequestTag {
|
class RequestTag(
|
||||||
|
val address: Address,
|
||||||
|
val relayHint: NormalizedRelayUrl? = null,
|
||||||
|
) {
|
||||||
|
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
|
||||||
|
|
||||||
|
fun toTagArray() = assemble(address, relayHint)
|
||||||
|
|
||||||
|
fun toTagIdOnly() = assemble(address, null)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG_NAME = "request"
|
const val TAG_NAME = "request"
|
||||||
|
|
||||||
fun isTag(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
|
fun isTagged(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && !Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)
|
||||||
|
|
||||||
fun parse(tag: Array<String>): String? {
|
fun isTagged(
|
||||||
|
tag: Array<String>,
|
||||||
|
addressId: String,
|
||||||
|
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId
|
||||||
|
|
||||||
|
fun isTagged(
|
||||||
|
tag: Array<String>,
|
||||||
|
address: ApprovedAddressTag,
|
||||||
|
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == address.toTag()
|
||||||
|
|
||||||
|
fun isIn(
|
||||||
|
tag: Array<String>,
|
||||||
|
addressIds: Set<String>,
|
||||||
|
) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): ApprovedAddressTag? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||||
|
|
||||||
|
val address = Address.parse(tag[1]) ?: return null
|
||||||
|
val relayHint = tag.getOrNull(2)?.let { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||||
|
return ApprovedAddressTag(address, relayHint)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseValidAddress(tag: Array<String>): String? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||||
|
return Address.parse(tag[1])?.toValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseAddress(tag: Array<String>): Address? {
|
||||||
ensure(tag.has(1)) { return null }
|
ensure(tag.has(1)) { return null }
|
||||||
ensure(tag[0] == TAG_NAME) { return null }
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
ensure(tag[1].isNotEmpty()) { return null }
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
val address = Address.parse(tag[1]) ?: return null
|
||||||
|
ensure(address.kind != CommunityDefinitionEvent.KIND) { return null }
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseAddressId(tag: Array<String>): String? {
|
||||||
|
ensure(tag.has(1)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||||
return tag[1]
|
return tag[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assemble(requestAddress: String) = arrayOf(TAG_NAME, requestAddress)
|
fun parseAsHint(tag: Array<String>): AddressHint? {
|
||||||
|
ensure(tag.has(2)) { return null }
|
||||||
|
ensure(tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null }
|
||||||
|
ensure(tag[1].contains(':')) { return null }
|
||||||
|
ensure(tag[2].isNotEmpty()) { return null }
|
||||||
|
|
||||||
|
val relayHint = RelayUrlNormalizer.normalizeOrNull(tag[2])
|
||||||
|
ensure(relayHint != null) { return null }
|
||||||
|
|
||||||
|
return AddressHint(tag[1], relayHint)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(
|
||||||
|
aTagId: HexKey,
|
||||||
|
relay: NormalizedRelayUrl?,
|
||||||
|
) = arrayOfNotNull(TAG_NAME, aTagId, relay?.url)
|
||||||
|
|
||||||
|
fun assemble(
|
||||||
|
address: Address,
|
||||||
|
relay: NormalizedRelayUrl?,
|
||||||
|
) = arrayOfNotNull(TAG_NAME, address.toValue(), relay?.url)
|
||||||
|
|
||||||
|
fun assemble(
|
||||||
|
kind: Int,
|
||||||
|
pubKey: String,
|
||||||
|
dTag: String,
|
||||||
|
relay: NormalizedRelayUrl?,
|
||||||
|
) = assemble(Address.assemble(kind, pubKey, dTag), relay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-2
@@ -21,13 +21,27 @@
|
|||||||
package com.vitorpamplona.quartz.experimental.attestations.request
|
package com.vitorpamplona.quartz.experimental.attestations.request
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.experimental.attestations.request.attestorPubKeys
|
||||||
|
import com.vitorpamplona.quartz.experimental.attestations.request.cashuToken
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import kotlin.let
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class AttestationRequestEvent(
|
class AttestationRequestEvent(
|
||||||
@@ -37,15 +51,45 @@ class AttestationRequestEvent(
|
|||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: HexKey,
|
sig: HexKey,
|
||||||
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||||
|
EventHintProvider,
|
||||||
|
AddressHintProvider,
|
||||||
|
PubKeyHintProvider {
|
||||||
|
override fun eventHints(): List<EventIdHint> = tags.mapNotNull(ETag::parseAsHint)
|
||||||
|
|
||||||
|
override fun linkedEventIds(): List<HexKey> = tags.mapNotNull(ETag::parseId)
|
||||||
|
|
||||||
|
override fun addressHints(): List<AddressHint> = tags.mapNotNull(ATag::parseAsHint)
|
||||||
|
|
||||||
|
override fun linkedAddressIds(): List<String> = tags.mapNotNull(ATag::parseAddressId)
|
||||||
|
|
||||||
|
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
|
||||||
|
|
||||||
|
override fun linkedPubKeys() = tags.mapNotNull(PTag::parseKey)
|
||||||
|
|
||||||
fun cashuToken() = tags.cashuToken()
|
fun cashuToken() = tags.cashuToken()
|
||||||
|
|
||||||
|
fun assertionAddrId() = tags.firstNotNullOfOrNull(ATag::parseAddressId)
|
||||||
|
|
||||||
|
fun assertionAddress() = tags.firstNotNullOfOrNull(ATag::parseAddress)
|
||||||
|
|
||||||
|
fun assertionATag() = tags.firstNotNullOfOrNull(ATag::parse)
|
||||||
|
|
||||||
|
fun assertionEventId() = tags.firstNotNullOfOrNull(ETag::parseId)
|
||||||
|
|
||||||
|
fun assertionETag() = tags.firstNotNullOfOrNull(ETag::parse)
|
||||||
|
|
||||||
|
fun assertionPubkey() = tags.firstNotNullOfOrNull(PTag::parseKey)
|
||||||
|
|
||||||
|
fun assertionPTag() = tags.firstNotNullOfOrNull(PTag::parse)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = 31872
|
const val KIND = 31872
|
||||||
const val ALT_DESCRIPTION = "Attestation Request"
|
const val ALT_DESCRIPTION = "Attestation Request"
|
||||||
|
|
||||||
fun build(
|
fun buildEvent(
|
||||||
dTagId: String,
|
dTagId: String,
|
||||||
|
about: EventHintBundle<Event>,
|
||||||
content: String = "",
|
content: String = "",
|
||||||
attestorPubKeys: List<HexKey> = emptyList(),
|
attestorPubKeys: List<HexKey> = emptyList(),
|
||||||
cashuToken: String? = null,
|
cashuToken: String? = null,
|
||||||
@@ -54,6 +98,41 @@ class AttestationRequestEvent(
|
|||||||
) = eventTemplate(KIND, content, createdAt) {
|
) = eventTemplate(KIND, content, createdAt) {
|
||||||
alt(ALT_DESCRIPTION)
|
alt(ALT_DESCRIPTION)
|
||||||
dTag(dTagId)
|
dTag(dTagId)
|
||||||
|
about(about)
|
||||||
|
attestorPubKeys(attestorPubKeys)
|
||||||
|
cashuToken?.let { cashuToken(it) }
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildReplaceable(
|
||||||
|
dTagId: String,
|
||||||
|
about: EventHintBundle<BaseReplaceableEvent>,
|
||||||
|
content: String = "",
|
||||||
|
attestorPubKeys: List<HexKey> = emptyList(),
|
||||||
|
cashuToken: String? = null,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<AttestationRequestEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, content, createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
dTag(dTagId)
|
||||||
|
aboutReplaceable(about)
|
||||||
|
attestorPubKeys(attestorPubKeys)
|
||||||
|
cashuToken?.let { cashuToken(it) }
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildAddress(
|
||||||
|
dTagId: String,
|
||||||
|
about: EventHintBundle<BaseAddressableEvent>,
|
||||||
|
content: String = "",
|
||||||
|
attestorPubKeys: List<HexKey> = emptyList(),
|
||||||
|
cashuToken: String? = null,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<AttestationRequestEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, content, createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
dTag(dTagId)
|
||||||
|
aboutAddressable(about)
|
||||||
attestorPubKeys(attestorPubKeys)
|
attestorPubKeys(attestorPubKeys)
|
||||||
cashuToken?.let { cashuToken(it) }
|
cashuToken?.let { cashuToken(it) }
|
||||||
initializer()
|
initializer()
|
||||||
|
|||||||
+12
@@ -21,10 +21,22 @@
|
|||||||
package com.vitorpamplona.quartz.experimental.attestations.request
|
package com.vitorpamplona.quartz.experimental.attestations.request
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.experimental.attestations.request.tags.CashuTokenTag
|
import com.vitorpamplona.quartz.experimental.attestations.request.tags.CashuTokenTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||||
|
|
||||||
fun TagArrayBuilder<AttestationRequestEvent>.attestorPubKeys(pubKeys: List<HexKey>) = addAll(pubKeys.map { PTag.assemble(it, null) })
|
fun TagArrayBuilder<AttestationRequestEvent>.attestorPubKeys(pubKeys: List<HexKey>) = addAll(pubKeys.map { PTag.assemble(it, null) })
|
||||||
|
|
||||||
fun TagArrayBuilder<AttestationRequestEvent>.cashuToken(token: String) = addUnique(CashuTokenTag.assemble(token))
|
fun TagArrayBuilder<AttestationRequestEvent>.cashuToken(token: String) = addUnique(CashuTokenTag.assemble(token))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<AttestationRequestEvent>.aboutAddressable(request: EventHintBundle<BaseAddressableEvent>) = addUnique(ATag.assemble(request.event.address(), request.relay))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<AttestationRequestEvent>.aboutReplaceable(request: EventHintBundle<BaseReplaceableEvent>) = addUnique(ATag.assemble(request.event.address(), request.relay))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<AttestationRequestEvent>.about(request: EventHintBundle<Event>) = addUnique(ETag.assemble(request.event.id, request.relay, request.event.pubKey))
|
||||||
|
|||||||
+7
-1
@@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable
|
|||||||
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.aTag
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.aTag
|
||||||
@@ -47,7 +48,12 @@ class CalendarRSVPEvent(
|
|||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: HexKey,
|
sig: HexKey,
|
||||||
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||||
|
PubKeyHintProvider {
|
||||||
|
override fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
|
||||||
|
|
||||||
|
override fun linkedPubKeys() = tags.mapNotNull(PTag::parseKey)
|
||||||
|
|
||||||
fun status() = tags.firstNotNullOfOrNull(RSVPStatusTag.Companion::parse)
|
fun status() = tags.firstNotNullOfOrNull(RSVPStatusTag.Companion::parse)
|
||||||
|
|
||||||
fun statusValue() = tags.firstNotNullOfOrNull(RSVPStatusTag.Companion::parseValue)
|
fun statusValue() = tags.firstNotNullOfOrNull(RSVPStatusTag.Companion::parseValue)
|
||||||
|
|||||||
Reference in New Issue
Block a user