feat: add attestation events to LocalCache, Home feed, profile feed, and NoteCompose UI
- Load AttestationEvent (31871), AttestationRequestEvent (31872), AttestorRecommendationEvent (31873), and AttestorProficiencyEvent (11871) into LocalCache via consumeBaseReplaceable - Create beautiful card-based UI renderers for all four attestation types with color-coded status indicators, validity badges, and kind chips - Add attestation kinds to Home feed data source (FilterHomePostsByAuthors) and DAL filter (HomeNewThreadFeedFilter) - Add attestation kinds to user profile feed data source (FilterUserProfilePosts) and DAL filter (UserProfileNewThreadFeedFilter) - Add string resources for attestation status labels https://claude.ai/code/session_01BEMFoHZENBwzmzS6TMPxVE
This commit is contained in:
@@ -39,6 +39,10 @@ import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
|
||||
import com.vitorpamplona.amethyst.service.BundledInsert
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.ui.note.dateFormatter
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
@@ -619,6 +623,30 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
wasVerified: Boolean,
|
||||
) = consumeBaseReplaceable(event, relay, wasVerified)
|
||||
|
||||
fun consume(
|
||||
event: AttestationEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
wasVerified: Boolean,
|
||||
) = consumeBaseReplaceable(event, relay, wasVerified)
|
||||
|
||||
fun consume(
|
||||
event: AttestationRequestEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
wasVerified: Boolean,
|
||||
) = consumeBaseReplaceable(event, relay, wasVerified)
|
||||
|
||||
fun consume(
|
||||
event: AttestorRecommendationEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
wasVerified: Boolean,
|
||||
) = consumeBaseReplaceable(event, relay, wasVerified)
|
||||
|
||||
fun consume(
|
||||
event: AttestorProficiencyEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
wasVerified: Boolean,
|
||||
) = consumeBaseReplaceable(event, relay, wasVerified)
|
||||
|
||||
fun consumeRegularEvent(
|
||||
event: Event,
|
||||
relay: NormalizedRelayUrl?,
|
||||
@@ -3051,6 +3079,10 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
is AppDefinitionEvent -> consume(event, relay, wasVerified)
|
||||
is AppRecommendationEvent -> consume(event, relay, wasVerified)
|
||||
is AppSpecificDataEvent -> consume(event, relay, wasVerified)
|
||||
is AttestationEvent -> consume(event, relay, wasVerified)
|
||||
is AttestationRequestEvent -> consume(event, relay, wasVerified)
|
||||
is AttestorRecommendationEvent -> consume(event, relay, wasVerified)
|
||||
is AttestorProficiencyEvent -> consume(event, relay, wasVerified)
|
||||
is AudioHeaderEvent -> consume(event, relay, wasVerified)
|
||||
is AudioTrackEvent -> consume(event, relay, wasVerified)
|
||||
is BadgeAwardEvent -> consume(event, relay, wasVerified)
|
||||
|
||||
@@ -102,6 +102,10 @@ import com.vitorpamplona.amethyst.ui.note.types.FileHeaderDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.types.FileStorageHeaderDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.types.PictureDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAppDefinition
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestation
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestationRequest
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestorProficiency
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestorRecommendation
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioTrack
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderBadgeAward
|
||||
@@ -174,6 +178,10 @@ import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.normalWithTopMarginNoteModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.bounties.bountyBaseReward
|
||||
@@ -767,6 +775,22 @@ private fun RenderNoteRow(
|
||||
RenderAppDefinition(baseNote, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is AttestationEvent -> {
|
||||
RenderAttestation(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is AttestationRequestEvent -> {
|
||||
RenderAttestationRequest(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is AttestorRecommendationEvent -> {
|
||||
RenderAttestorRecommendation(baseNote, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is AttestorProficiencyEvent -> {
|
||||
RenderAttestorProficiency(baseNote, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is AudioTrackEvent -> {
|
||||
RenderAudioTrack(baseNote, ContentScale.FillWidth, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.note.types
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.HourglassTop
|
||||
import androidx.compose.material.icons.filled.Recommend
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.filled.VerifiedUser
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
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.Validity
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun RenderAttestation(
|
||||
note: Note,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? AttestationEvent ?: return
|
||||
|
||||
val validity = remember(noteEvent) { noteEvent.validity() }
|
||||
val status = remember(noteEvent) { noteEvent.status() }
|
||||
val validFrom = remember(noteEvent) { noteEvent.validFrom() }
|
||||
val validTo = remember(noteEvent) { noteEvent.validTo() }
|
||||
val content = remember(noteEvent) { noteEvent.content.ifBlank { null } }
|
||||
|
||||
val statusColor = remember(status, validity) { attestationColor(status, validity) }
|
||||
val statusIcon = remember(status, validity) { attestationIcon(status, validity) }
|
||||
val statusLabel = attestationStatusLabel(status, validity)
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.border(1.dp, statusColor.copy(alpha = 0.3f), RoundedCornerShape(12.dp))
|
||||
.background(statusColor.copy(alpha = 0.06f))
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = statusIcon,
|
||||
contentDescription = stringRes(R.string.attestation),
|
||||
tint = statusColor,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
Text(
|
||||
text = statusLabel,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = statusColor,
|
||||
)
|
||||
}
|
||||
|
||||
if (validFrom != null || validTo != null) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
validFrom?.let {
|
||||
Text(
|
||||
text = stringRes(R.string.attestation_valid_from, formatTimestamp(it)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
validTo?.let {
|
||||
Text(
|
||||
text = stringRes(R.string.attestation_valid_to, formatTimestamp(it)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content?.let {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (quotesLeft > 0) {
|
||||
note.replyTo?.firstOrNull()?.let {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringRes(R.string.attestation_attests_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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderAttestationRequest(
|
||||
note: Note,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? AttestationRequestEvent ?: return
|
||||
val content = remember(noteEvent) { noteEvent.content.ifBlank { null } }
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
|
||||
RoundedCornerShape(12.dp),
|
||||
).background(MaterialTheme.colorScheme.primary.copy(alpha = 0.06f))
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Send,
|
||||
contentDescription = stringRes(R.string.attestation_request),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringRes(R.string.attestation_request),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
|
||||
content?.let {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (quotesLeft > 0) {
|
||||
note.replyTo?.firstOrNull()?.let {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
NoteCompose(
|
||||
baseNote = it,
|
||||
modifier = MaterialTheme.colorScheme.replyModifier,
|
||||
isQuotedNote = true,
|
||||
unPackReply = ReplyRenderType.NONE,
|
||||
makeItShort = true,
|
||||
quotesLeft = quotesLeft - 1,
|
||||
parentBackgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RenderAttestorRecommendation(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? AttestorRecommendationEvent ?: return
|
||||
val kinds = remember(noteEvent) { noteEvent.kinds() }
|
||||
val description = remember(noteEvent) { noteEvent.description() }
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colorScheme.tertiary.copy(alpha = 0.3f),
|
||||
RoundedCornerShape(12.dp),
|
||||
).background(MaterialTheme.colorScheme.tertiary.copy(alpha = 0.06f))
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Recommend,
|
||||
contentDescription = stringRes(R.string.attestor_recommendation),
|
||||
tint = MaterialTheme.colorScheme.tertiary,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringRes(R.string.attestor_recommendation),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
if (kinds.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text =
|
||||
stringRes(
|
||||
R.string.attestor_recommendation_for_kinds,
|
||||
kinds.joinToString(", "),
|
||||
),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
description?.let {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RenderAttestorProficiency(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? AttestorProficiencyEvent ?: return
|
||||
val kinds = remember(noteEvent) { noteEvent.kinds() }
|
||||
val description = remember(noteEvent) { noteEvent.description() }
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.border(
|
||||
1.dp,
|
||||
MaterialTheme.colorScheme.secondary.copy(alpha = 0.3f),
|
||||
RoundedCornerShape(12.dp),
|
||||
).background(MaterialTheme.colorScheme.secondary.copy(alpha = 0.06f))
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Star,
|
||||
contentDescription = stringRes(R.string.attestor_proficiency),
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringRes(R.string.attestor_proficiency),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
)
|
||||
}
|
||||
|
||||
if (kinds.isNotEmpty()) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
kinds.forEach { kind ->
|
||||
Text(
|
||||
text = "Kind $kind",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier =
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(MaterialTheme.colorScheme.secondary.copy(alpha = 0.12f))
|
||||
.padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
description?.let {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attestationColor(
|
||||
status: AttestationStatus?,
|
||||
validity: Validity?,
|
||||
): Color =
|
||||
when {
|
||||
status == AttestationStatus.REVOKED -> Color(0xFFB71C1C)
|
||||
status == AttestationStatus.REJECTED -> Color(0xFFB71C1C)
|
||||
validity == Validity.INVALID -> Color(0xFFB71C1C)
|
||||
status == AttestationStatus.VERIFIED -> Color(0xFF2E7D32)
|
||||
validity == Validity.VALID -> Color(0xFF2E7D32)
|
||||
status == AttestationStatus.VERIFYING -> Color(0xFFF57F17)
|
||||
status == AttestationStatus.ACCEPTED -> Color(0xFF1565C0)
|
||||
else -> Color(0xFF757575)
|
||||
}
|
||||
|
||||
private fun attestationIcon(
|
||||
status: AttestationStatus?,
|
||||
validity: Validity?,
|
||||
): ImageVector =
|
||||
when {
|
||||
status == AttestationStatus.REVOKED -> Icons.Default.Close
|
||||
status == AttestationStatus.REJECTED -> Icons.Default.Close
|
||||
validity == Validity.INVALID -> Icons.Default.Close
|
||||
status == AttestationStatus.VERIFIED -> Icons.Default.VerifiedUser
|
||||
validity == Validity.VALID -> Icons.Default.CheckCircle
|
||||
status == AttestationStatus.VERIFYING -> Icons.Default.HourglassTop
|
||||
status == AttestationStatus.ACCEPTED -> Icons.Default.CheckCircle
|
||||
else -> Icons.Default.VerifiedUser
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun attestationStatusLabel(
|
||||
status: AttestationStatus?,
|
||||
validity: Validity?,
|
||||
): String =
|
||||
when {
|
||||
status == AttestationStatus.REVOKED -> stringRes(R.string.attestation_status_revoked)
|
||||
status == AttestationStatus.REJECTED -> stringRes(R.string.attestation_status_rejected)
|
||||
validity == Validity.INVALID -> stringRes(R.string.attestation_invalid)
|
||||
status == AttestationStatus.VERIFIED -> stringRes(R.string.attestation_status_verified)
|
||||
validity == Validity.VALID -> stringRes(R.string.attestation_valid)
|
||||
status == AttestationStatus.VERIFYING -> stringRes(R.string.attestation_status_verifying)
|
||||
status == AttestationStatus.ACCEPTED -> stringRes(R.string.attestation_status_accepted)
|
||||
else -> stringRes(R.string.attestation)
|
||||
}
|
||||
|
||||
private fun formatTimestamp(timestamp: Long): String {
|
||||
val sdf = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault())
|
||||
return sdf.format(Date(timestamp * 1000))
|
||||
}
|
||||
+13
-1
@@ -29,6 +29,10 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthors
|
||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
@@ -60,6 +64,10 @@ class HomeNewThreadFeedFilter(
|
||||
LongTextNoteEvent.KIND,
|
||||
LiveChessGameChallengeEvent.KIND,
|
||||
LiveChessGameEndEvent.KIND,
|
||||
AttestationEvent.KIND,
|
||||
AttestationRequestEvent.KIND,
|
||||
AttestorRecommendationEvent.KIND,
|
||||
AttestorProficiencyEvent.KIND,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -126,7 +134,11 @@ class HomeNewThreadFeedFilter(
|
||||
noteEvent is AudioHeaderEvent ||
|
||||
noteEvent is ChessGameEvent ||
|
||||
noteEvent is LiveChessGameChallengeEvent ||
|
||||
noteEvent is LiveChessGameEndEvent
|
||||
noteEvent is LiveChessGameEndEvent ||
|
||||
noteEvent is AttestationEvent ||
|
||||
noteEvent is AttestationRequestEvent ||
|
||||
noteEvent is AttestorRecommendationEvent ||
|
||||
noteEvent is AttestorProficiencyEvent
|
||||
) &&
|
||||
filterParams.match(noteEvent, it.relays) &&
|
||||
it.isNewThread()
|
||||
|
||||
+8
@@ -23,6 +23,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip65Follo
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
@@ -66,6 +70,10 @@ val HomePostsNewThreadKinds =
|
||||
ChessGameEvent.KIND,
|
||||
LiveChessGameChallengeEvent.KIND,
|
||||
LiveChessGameEndEvent.KIND,
|
||||
AttestationEvent.KIND,
|
||||
AttestationRequestEvent.KIND,
|
||||
AttestorRecommendationEvent.KIND,
|
||||
AttestorProficiencyEvent.KIND,
|
||||
)
|
||||
|
||||
val HomePostsConversationKinds =
|
||||
|
||||
+8
@@ -23,6 +23,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.datasource
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
@@ -66,6 +70,10 @@ val UserProfilePostKinds2 =
|
||||
VoiceReplyEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
PinListEvent.KIND,
|
||||
AttestationEvent.KIND,
|
||||
AttestationRequestEvent.KIND,
|
||||
AttestorRecommendationEvent.KIND,
|
||||
AttestorProficiencyEvent.KIND,
|
||||
)
|
||||
|
||||
fun filterUserProfilePosts(
|
||||
|
||||
+9
-1
@@ -27,6 +27,10 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.proficiency.AttestorProficiencyEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.recommendation.AttestorRecommendationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.request.AttestationRequestEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
@@ -87,7 +91,11 @@ class UserProfileNewThreadFeedFilter(
|
||||
it.event is AudioTrackEvent ||
|
||||
it.event is AudioHeaderEvent ||
|
||||
it.event is VoiceEvent ||
|
||||
it.event is TorrentEvent
|
||||
it.event is TorrentEvent ||
|
||||
it.event is AttestationEvent ||
|
||||
it.event is AttestationRequestEvent ||
|
||||
it.event is AttestorRecommendationEvent ||
|
||||
it.event is AttestorProficiencyEvent
|
||||
) &&
|
||||
it.isNewThread() &&
|
||||
account.isAcceptable(it)
|
||||
|
||||
@@ -1873,4 +1873,22 @@
|
||||
<string name="event_sync_status_downloading">Downloading</string>
|
||||
<string name="event_sync_status_error">Error</string>
|
||||
<string name="event_sync_status_completed">Completed</string>
|
||||
|
||||
<string name="attestation">Attestation</string>
|
||||
<string name="attestation_valid">Valid</string>
|
||||
<string name="attestation_invalid">Invalid</string>
|
||||
<string name="attestation_status_accepted">Accepted</string>
|
||||
<string name="attestation_status_rejected">Rejected</string>
|
||||
<string name="attestation_status_verifying">Verifying</string>
|
||||
<string name="attestation_status_verified">Verified</string>
|
||||
<string name="attestation_status_revoked">Revoked</string>
|
||||
<string name="attestation_valid_from">Valid from %1$s</string>
|
||||
<string name="attestation_valid_to">Valid to %1$s</string>
|
||||
<string name="attestation_request">Attestation Request</string>
|
||||
<string name="attestation_request_description">Requesting attestation for an event</string>
|
||||
<string name="attestor_recommendation">Attestor Recommendation</string>
|
||||
<string name="attestor_recommendation_for_kinds">Recommended for kinds: %1$s</string>
|
||||
<string name="attestor_proficiency">Attestor Proficiency</string>
|
||||
<string name="attestor_proficiency_for_kinds">Proficient in verifying kinds: %1$s</string>
|
||||
<string name="attestation_attests_to">Attests to</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user