Display statuses if NIP05 succeeds.
This commit is contained in:
@@ -15,7 +15,9 @@ import com.vitorpamplona.quartz.encoders.decodePublicKeyAsHexOrNull
|
|||||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||||
import com.vitorpamplona.quartz.events.*
|
import com.vitorpamplona.quartz.events.*
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@@ -343,7 +345,27 @@ object LocalCache {
|
|||||||
private fun consume(event: PinListEvent) { consumeBaseReplaceable(event) }
|
private fun consume(event: PinListEvent) { consumeBaseReplaceable(event) }
|
||||||
private fun consume(event: RelaySetEvent) { consumeBaseReplaceable(event) }
|
private fun consume(event: RelaySetEvent) { consumeBaseReplaceable(event) }
|
||||||
private fun consume(event: AudioTrackEvent) { consumeBaseReplaceable(event) }
|
private fun consume(event: AudioTrackEvent) { consumeBaseReplaceable(event) }
|
||||||
private fun consume(event: StatusEvent, relay: Relay?) { consumeBaseReplaceable(event) }
|
private fun consume(event: StatusEvent, relay: Relay?) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
version.moveAllReferencesTo(note)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already processed this event.
|
||||||
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
|
note.loadEvent(event, author, emptyList())
|
||||||
|
|
||||||
|
author.liveSet?.statuses?.invalidateData()
|
||||||
|
|
||||||
|
refreshObservers(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun consume(event: BadgeDefinitionEvent) { consumeBaseReplaceable(event) }
|
fun consume(event: BadgeDefinitionEvent) { consumeBaseReplaceable(event) }
|
||||||
|
|
||||||
@@ -1127,6 +1149,18 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun findStatusesForUser(user: User): ImmutableList<AddressableNote> {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
return addressables.filter {
|
||||||
|
val noteEvent = it.value.event
|
||||||
|
(noteEvent is StatusEvent && noteEvent.pubKey == user.pubkeyHex && !noteEvent.isExpired() && noteEvent.content.isNotBlank())
|
||||||
|
}.values
|
||||||
|
.sortedWith(compareBy({ it.event?.expiration() ?: it.event?.createdAt() }, { it.idHex }))
|
||||||
|
.reversed()
|
||||||
|
.toImmutableList()
|
||||||
|
}
|
||||||
|
|
||||||
fun cleanObservers() {
|
fun cleanObservers() {
|
||||||
notes.forEach {
|
notes.forEach {
|
||||||
it.value.clearLive()
|
it.value.clearLive()
|
||||||
|
|||||||
@@ -392,11 +392,16 @@ class UserLiveSet(u: User) {
|
|||||||
val metadata: UserLiveData = UserLiveData(u)
|
val metadata: UserLiveData = UserLiveData(u)
|
||||||
val zaps: UserLiveData = UserLiveData(u)
|
val zaps: UserLiveData = UserLiveData(u)
|
||||||
val bookmarks: UserLiveData = UserLiveData(u)
|
val bookmarks: UserLiveData = UserLiveData(u)
|
||||||
|
val statuses: UserLiveData = UserLiveData(u)
|
||||||
|
|
||||||
val profilePictureChanges = metadata.map {
|
val profilePictureChanges = metadata.map {
|
||||||
it.user.profilePicture()
|
it.user.profilePicture()
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
|
val nip05Changes = metadata.map {
|
||||||
|
it.user.nip05()
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
val userMetadataInfo = metadata.map {
|
val userMetadataInfo = metadata.map {
|
||||||
it.user.info
|
it.user.info
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
|||||||
@@ -5,38 +5,48 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.text.ClickableText
|
import androidx.compose.foundation.text.ClickableText
|
||||||
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.LocalTextStyle
|
import androidx.compose.material.LocalTextStyle
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.map
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.LoadAnyAddressableNote
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05CheckingIcon
|
import com.vitorpamplona.amethyst.ui.note.NIP05CheckingIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05FailedVerification
|
import com.vitorpamplona.amethyst.ui.note.NIP05FailedVerification
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05VerifiedIcon
|
import com.vitorpamplona.amethyst.ui.note.NIP05VerifiedIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||||
import com.vitorpamplona.amethyst.ui.theme.NIP05IconSize
|
import com.vitorpamplona.amethyst.ui.theme.NIP05IconSize
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size18Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.nip05
|
import com.vitorpamplona.amethyst.ui.theme.nip05
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import com.vitorpamplona.quartz.events.AddressableEvent
|
||||||
import com.vitorpamplona.quartz.events.UserMetadata
|
import com.vitorpamplona.quartz.events.UserMetadata
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun nip05VerificationAsAState(userMetadata: UserMetadata, pubkeyHex: String): MutableState<Boolean?> {
|
fun nip05VerificationAsAState(userMetadata: UserMetadata, pubkeyHex: String): MutableState<Boolean?> {
|
||||||
@@ -94,12 +104,7 @@ fun nip05VerificationAsAState(userMetadata: UserMetadata, pubkeyHex: String): Mu
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifier) {
|
fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifier) {
|
||||||
val noteState by baseNote.live().metadata.observeAsState()
|
val author by baseNote.live().authorChanges.observeAsState()
|
||||||
val author by remember(noteState) {
|
|
||||||
derivedStateOf {
|
|
||||||
noteState?.note?.author
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
author?.let {
|
author?.let {
|
||||||
ObserveDisplayNip05Status(it, columnModifier)
|
ObserveDisplayNip05Status(it, columnModifier)
|
||||||
@@ -108,35 +113,88 @@ fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifie
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifier) {
|
fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifier) {
|
||||||
val nip05 by baseUser.live().metadata.map {
|
val nip05 by baseUser.live().nip05Changes.observeAsState(baseUser.nip05())
|
||||||
it.user.nip05()
|
|
||||||
}.observeAsState(baseUser.nip05())
|
|
||||||
|
|
||||||
Crossfade(targetState = nip05, modifier = columnModifier) {
|
LoadAnyAddressableNote(baseUser) { statuses ->
|
||||||
if (it != null) {
|
Crossfade(targetState = nip05, modifier = columnModifier, label = "ObserveDisplayNip05StatusCrossfade") {
|
||||||
DisplayNIP05Line(it, baseUser, columnModifier)
|
if (it != null) {
|
||||||
} else {
|
VerifyAndDisplayNIP05OrStatusLine(it, statuses, baseUser, columnModifier)
|
||||||
Text(
|
} else {
|
||||||
text = baseUser.pubkeyDisplayHex(),
|
Text(
|
||||||
color = MaterialTheme.colors.placeholderText,
|
text = baseUser.pubkeyDisplayHex(),
|
||||||
maxLines = 1,
|
color = MaterialTheme.colors.placeholderText,
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
modifier = columnModifier
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
modifier = columnModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DisplayNIP05Line(nip05: String, baseUser: User, columnModifier: Modifier = Modifier) {
|
private fun VerifyAndDisplayNIP05OrStatusLine(
|
||||||
|
nip05: String,
|
||||||
|
statuses: ImmutableList<AddressableNote>,
|
||||||
|
baseUser: User,
|
||||||
|
columnModifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var displayStatusOrNIP05 by remember {
|
||||||
|
mutableStateOf<Int>(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val nip05Verified = nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex)
|
||||||
|
|
||||||
Column(modifier = columnModifier) {
|
Column(modifier = columnModifier) {
|
||||||
val nip05Verified = nip05VerificationAsAState(baseUser.info!!, baseUser.pubkeyHex)
|
Crossfade(targetState = displayStatusOrNIP05, label = "NIP05StatusRotationCrossfade") {
|
||||||
Crossfade(targetState = nip05Verified) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
DisplayNIP05(nip05, it)
|
if (statuses.isEmpty()) {
|
||||||
|
DisplayNIP05(nip05, nip05Verified)
|
||||||
|
} else if (nip05Verified.value != true || it < 0 || it >= statuses.size) {
|
||||||
|
DisplayNIP05(nip05, nip05Verified)
|
||||||
|
} else {
|
||||||
|
DisplayStatus(statuses[it])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (statuses.size > 1) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
delay(10.seconds)
|
||||||
|
displayStatusOrNIP05 = ((displayStatusOrNIP05 + 1) % (statuses.size + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayStatus(addressableNote: AddressableNote) {
|
||||||
|
val noteState by addressableNote.live().metadata.observeAsState()
|
||||||
|
|
||||||
|
val content = remember(noteState) { addressableNote.event?.content() ?: "" }
|
||||||
|
val type = remember(noteState) {
|
||||||
|
(addressableNote.event as? AddressableEvent)?.dTag() ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
when (type) {
|
||||||
|
"music" -> Icon(
|
||||||
|
painter = painterResource(id = R.drawable.tunestr),
|
||||||
|
null,
|
||||||
|
modifier = Size18Modifier,
|
||||||
|
tint = MaterialTheme.colors.placeholderText
|
||||||
|
)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = content,
|
||||||
|
fontSize = Font14SP,
|
||||||
|
color = MaterialTheme.colors.placeholderText,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -1052,7 +1052,7 @@ private fun NoteBody(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = StdVertSpacer)
|
Spacer(modifier = Modifier.height(3.dp))
|
||||||
|
|
||||||
if (!makeItShort) {
|
if (!makeItShort) {
|
||||||
ReplyRow(
|
ReplyRow(
|
||||||
@@ -2506,6 +2506,30 @@ fun SecondUserInfoRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadAnyAddressableNote(
|
||||||
|
user: User,
|
||||||
|
content: @Composable (ImmutableList<AddressableNote>) -> Unit
|
||||||
|
) {
|
||||||
|
var statuses: ImmutableList<AddressableNote> by remember {
|
||||||
|
mutableStateOf(persistentListOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
val userStatus = user.live().statuses.observeAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = userStatus) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
val myUser = userStatus.value?.user ?: return@launch
|
||||||
|
val newStatuses = LocalCache.findStatusesForUser(myUser)
|
||||||
|
if (!equalImmutableLists(statuses, newStatuses)) {
|
||||||
|
statuses = newStatuses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content(statuses)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayLocation(geohash: String, nav: (String) -> Unit) {
|
fun DisplayLocation(geohash: String, nav: (String) -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|||||||
Reference in New Issue
Block a user