Adds support for RelaySet event types.

pull/444/head^2
Vitor Pamplona 2023-06-07 17:16:48 -04:00
rodzic ac0103a53e
commit 89bdc9dd58
15 zmienionych plików z 251 dodań i 26 usunięć

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -39,7 +39,7 @@ abstract class NostrDataSource(val debugName: String) {
eventCounter = eventCounter + Pair(key, Counter(1))
}
LocalCache.consume(event, relay)
LocalCache.verifyAndConsume(event, relay)
}
}

Wyświetl plik

@ -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)

Wyświetl plik

@ -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())
}
}
}

Wyświetl plik

@ -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)

Wyświetl plik

@ -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,

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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(

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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)
}
}
}

Wyświetl plik

@ -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()

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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)
}
}
}