kopia lustrzana https://github.com/vitorpamplona/amethyst
Adds support for RelaySet event types.
rodzic
ac0103a53e
commit
89bdc9dd58
|
@ -300,6 +300,19 @@ object LocalCache {
|
|||
}
|
||||
}
|
||||
|
||||
private fun consume(event: RelaySetEvent) {
|
||||
val note = getOrCreateAddressableNote(event.address())
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
if (note.event?.id() == event.id()) return
|
||||
|
||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
refreshObservers(note)
|
||||
}
|
||||
}
|
||||
|
||||
private fun consume(event: AudioTrackEvent) {
|
||||
val note = getOrCreateAddressableNote(event.address())
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
@ -1015,7 +1028,7 @@ object LocalCache {
|
|||
live.invalidateData(newNote)
|
||||
}
|
||||
|
||||
fun consume(event: Event, relay: Relay?) {
|
||||
fun verifyAndConsume(event: Event, relay: Relay?) {
|
||||
checkNotInMainThread()
|
||||
|
||||
if (!event.hasValidSignature()) {
|
||||
|
@ -1050,7 +1063,7 @@ object LocalCache {
|
|||
is HighlightEvent -> consume(event, relay)
|
||||
is LnZapEvent -> {
|
||||
event.zapRequest?.let {
|
||||
consume(it, relay)
|
||||
verifyAndConsume(it, relay)
|
||||
}
|
||||
consume(event)
|
||||
}
|
||||
|
@ -1064,10 +1077,11 @@ object LocalCache {
|
|||
is PeopleListEvent -> consume(event)
|
||||
is ReactionEvent -> consume(event)
|
||||
is RecommendRelayEvent -> consume(event)
|
||||
is RelaySetEvent -> consume(event)
|
||||
is ReportEvent -> consume(event, relay)
|
||||
is RepostEvent -> {
|
||||
event.containedPost()?.let {
|
||||
consume(it, relay)
|
||||
verifyAndConsume(it, relay)
|
||||
}
|
||||
consume(event)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ abstract class NostrDataSource(val debugName: String) {
|
|||
eventCounter = eventCounter + Pair(key, Counter(1))
|
||||
}
|
||||
|
||||
LocalCache.consume(event, relay)
|
||||
LocalCache.verifyAndConsume(event, relay)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -258,6 +258,7 @@ open class Event(
|
|||
PrivateDmEvent.kind -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ReactionEvent.kind -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RecommendRelayEvent.kind -> RecommendRelayEvent(id, pubKey, createdAt, tags, content, sig, lenient)
|
||||
RelaySetEvent.kind -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ReportEvent.kind -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
RepostEvent.kind -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
TextNoteEvent.kind -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package com.vitorpamplona.amethyst.service.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.model.HexKey
|
||||
import com.vitorpamplona.amethyst.model.toHexKey
|
||||
import nostr.postr.Utils
|
||||
import java.util.Date
|
||||
|
||||
@Immutable
|
||||
class RelaySetEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: List<List<String>>,
|
||||
content: String,
|
||||
sig: HexKey
|
||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig), AddressableEvent {
|
||||
|
||||
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
|
||||
override fun address() = ATag(kind, pubKey, dTag(), null)
|
||||
|
||||
fun relays() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] }
|
||||
fun description() = tags.firstOrNull() { it.size > 1 && it[0] == "description" }?.get(1)
|
||||
|
||||
companion object {
|
||||
const val kind = 30022
|
||||
|
||||
fun create(
|
||||
relays: List<String>,
|
||||
privateKey: ByteArray,
|
||||
createdAt: Long = Date().time / 1000
|
||||
): RelaySetEvent {
|
||||
val tags = mutableListOf<List<String>>()
|
||||
relays.forEach {
|
||||
tags.add(listOf("r", it))
|
||||
}
|
||||
|
||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||
val id = generateId(pubKey, createdAt, kind, tags, "")
|
||||
val sig = Utils.sign(id, privateKey)
|
||||
return RelaySetEvent(id.toHexKey(), pubKey, createdAt, tags, "", sig.toHexKey())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
|||
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||
scope.launch {
|
||||
// adds to database
|
||||
LocalCache.consume(event, null)
|
||||
LocalCache.verifyAndConsume(event, null)
|
||||
|
||||
when (event) {
|
||||
is PrivateDmEvent -> notify(event)
|
||||
|
|
|
@ -97,7 +97,7 @@ fun ExpandableRichTextViewer(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ShowMoreButton(onClick: () -> Unit) {
|
||||
public fun ShowMoreButton(onClick: () -> Unit) {
|
||||
Button(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
onClick = onClick,
|
||||
|
|
|
@ -372,10 +372,9 @@ private fun aspectRatio(dim: String?): Float? {
|
|||
@Composable
|
||||
private fun DisplayUrlWithLoadingSymbol(content: ZoomableContent) {
|
||||
var cnt by remember { mutableStateOf<ZoomableContent?>(null) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
delay(200)
|
||||
cnt = content
|
||||
}
|
||||
|
|
|
@ -70,7 +70,6 @@ import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
|||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
val ChatBubbleShapeMe = RoundedCornerShape(15.dp, 15.dp, 3.dp, 15.dp)
|
||||
val ChatBubbleShapeThem = RoundedCornerShape(3.dp, 15.dp, 15.dp, 15.dp)
|
||||
|
@ -348,7 +347,7 @@ fun ChatTimeAgo(time: Long) {
|
|||
var timeStr by remember { mutableStateOf("") }
|
||||
|
||||
LaunchedEffect(key1 = time) {
|
||||
withContext(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
timeStr = timeAgoShort(time, context = context)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Alignment.Companion.TopEnd
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -109,10 +110,12 @@ import com.vitorpamplona.amethyst.service.model.PinListEvent
|
|||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RelaySetEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||
|
@ -123,6 +126,7 @@ import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
|||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.VideoView
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContent
|
||||
|
@ -510,6 +514,10 @@ fun NormalNote(
|
|||
RenderPeopleList(baseNote, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is RelaySetEvent -> {
|
||||
RelaySetList(baseNote, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
is AudioTrackEvent -> {
|
||||
RenderAudioTrack(baseNote, accountViewModel, nav)
|
||||
}
|
||||
|
@ -958,6 +966,23 @@ private fun RenderPrivateMessage(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RelaySetList(
|
||||
baseNote: Note,
|
||||
backgroundColor: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav)
|
||||
|
||||
ReactionsRow(baseNote, accountViewModel, nav)
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderPeopleList(
|
||||
baseNote: Note,
|
||||
|
@ -975,6 +1000,139 @@ fun RenderPeopleList(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayRelaySet(
|
||||
baseNote: Note,
|
||||
backgroundColor: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit
|
||||
) {
|
||||
val noteEvent = baseNote.event as? RelaySetEvent ?: return
|
||||
|
||||
val relays by remember {
|
||||
mutableStateOf<ImmutableList<String>>(
|
||||
noteEvent.relays().toImmutableList()
|
||||
)
|
||||
}
|
||||
|
||||
var expanded by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
val toMembersShow = if (expanded) {
|
||||
relays
|
||||
} else {
|
||||
relays.take(3)
|
||||
}
|
||||
|
||||
val relayListName by remember {
|
||||
derivedStateOf {
|
||||
"#${noteEvent.dTag()}"
|
||||
}
|
||||
}
|
||||
|
||||
val relayDescription by remember {
|
||||
derivedStateOf {
|
||||
noteEvent.description()
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = relayListName,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(5.dp),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
relayDescription?.let {
|
||||
Text(
|
||||
text = it,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(5.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
color = Color.Gray
|
||||
)
|
||||
}
|
||||
|
||||
Box {
|
||||
Column(modifier = Modifier.padding(top = 5.dp)) {
|
||||
toMembersShow.forEach { relay ->
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = CenterVertically) {
|
||||
Text(
|
||||
relay.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/"),
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, bottom = 5.dp)
|
||||
.weight(1f)
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
RelayOptionsAction(relay, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (relays.size > 3 && !expanded) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
backgroundColor.copy(alpha = 0f),
|
||||
backgroundColor
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
ShowMoreButton {
|
||||
expanded = !expanded
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RelayOptionsAction(
|
||||
relay: String,
|
||||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
val userStateRelayInfo by accountViewModel.account.userProfile().live().relayInfo.observeAsState()
|
||||
val isCurrentlyOnTheUsersList by remember(userStateRelayInfo) {
|
||||
derivedStateOf {
|
||||
userStateRelayInfo?.user?.latestContactList?.relays()?.none { it.key == relay } == true
|
||||
}
|
||||
}
|
||||
|
||||
var wantsToAddRelay by remember {
|
||||
mutableStateOf("")
|
||||
}
|
||||
|
||||
if (wantsToAddRelay.isNotEmpty()) {
|
||||
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay)
|
||||
}
|
||||
|
||||
if (isCurrentlyOnTheUsersList) {
|
||||
AddRelayButton { wantsToAddRelay = relay }
|
||||
} else {
|
||||
RemoveRelayButton { wantsToAddRelay = relay }
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun DisplayPeopleList(
|
||||
|
|
|
@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
|||
import com.vitorpamplona.amethyst.service.model.PeopleListEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PinListEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RelaySetEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||
|
@ -374,6 +375,13 @@ fun NoteMaster(
|
|||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
} else if (noteEvent is RelaySetEvent) {
|
||||
DisplayRelaySet(
|
||||
baseNote,
|
||||
MaterialTheme.colors.background,
|
||||
accountViewModel,
|
||||
nav
|
||||
)
|
||||
} else if (noteEvent is AppDefinitionEvent) {
|
||||
RenderAppDefinition(baseNote, accountViewModel, nav)
|
||||
} else if (noteEvent is HighlightEvent) {
|
||||
|
|
|
@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.ui.screen.ChatroomListFeedView
|
|||
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
@ -150,9 +151,11 @@ fun ChatroomListScreen(
|
|||
@Composable
|
||||
fun WatchAccountForListScreen(knownFeedViewModel: NostrChatroomListKnownFeedViewModel, newFeedViewModel: NostrChatroomListNewFeedViewModel, accountViewModel: AccountViewModel) {
|
||||
LaunchedEffect(accountViewModel) {
|
||||
NostrChatroomListDataSource.start()
|
||||
knownFeedViewModel.invalidateData(true)
|
||||
newFeedViewModel.invalidateData(true)
|
||||
launch(Dispatchers.IO) {
|
||||
NostrChatroomListDataSource.start()
|
||||
knownFeedViewModel.invalidateData(true)
|
||||
newFeedViewModel.invalidateData(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ fun ChatroomScreen(
|
|||
var userRoom by remember { mutableStateOf<User?>(null) }
|
||||
|
||||
LaunchedEffect(userId) {
|
||||
withContext(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
val newUser = LocalCache.checkGetOrCreateUser(userId)
|
||||
if (newUser != userRoom) {
|
||||
userRoom = newUser
|
||||
|
@ -105,10 +105,10 @@ fun ChatroomScreen(
|
|||
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||
|
||||
LaunchedEffect(baseUser, accountViewModel) {
|
||||
NostrChatroomDataSource.start()
|
||||
feedViewModel.invalidateData()
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
NostrChatroomDataSource.start()
|
||||
feedViewModel.invalidateData()
|
||||
|
||||
newPostModel.imageUploadingError.collect { error ->
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
|
||||
|
|
|
@ -138,17 +138,15 @@ fun WatchAccountForHomeScreen(
|
|||
accountViewModel: AccountViewModel
|
||||
) {
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = remember(accountState) { accountState?.account } ?: return
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var firstTime by remember(accountViewModel) { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(accountViewModel, account.defaultHomeFollowList) {
|
||||
LaunchedEffect(accountViewModel, accountState?.account?.defaultHomeFollowList) {
|
||||
// Only invalidate when things change. Not in the first run
|
||||
if (firstTime) {
|
||||
firstTime = false
|
||||
} else {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
NostrHomeDataSource.invalidateFilters()
|
||||
homeFeedViewModel.invalidateDataAndSendToTop(true)
|
||||
repliesFeedViewModel.invalidateDataAndSendToTop(true)
|
||||
|
|
|
@ -26,7 +26,6 @@ import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
|||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun LoadRedirectScreen(eventId: String?, navController: NavController) {
|
||||
|
@ -42,7 +41,7 @@ fun LoadRedirectScreen(eventId: String?, navController: NavController) {
|
|||
}
|
||||
|
||||
LaunchedEffect(eventId) {
|
||||
withContext(Dispatchers.IO) {
|
||||
launch(Dispatchers.IO) {
|
||||
val newNoteBase = LocalCache.checkGetOrCreateNote(eventId)
|
||||
if (newNoteBase != noteBase) {
|
||||
noteBase = newNoteBase
|
||||
|
|
|
@ -145,9 +145,11 @@ fun SearchScreen(
|
|||
@Composable
|
||||
fun WatchAccountForSearchScreen(searchFeedViewModel: NostrGlobalFeedViewModel, accountViewModel: AccountViewModel) {
|
||||
LaunchedEffect(accountViewModel) {
|
||||
NostrGlobalDataSource.resetFilters()
|
||||
NostrSearchEventOrUserDataSource.start()
|
||||
searchFeedViewModel.invalidateData(true)
|
||||
launch(Dispatchers.IO) {
|
||||
NostrGlobalDataSource.resetFilters()
|
||||
NostrSearchEventOrUserDataSource.start()
|
||||
searchFeedViewModel.invalidateData(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue