Continues to move away from starting co-routines directly in composables

This commit is contained in:
Vitor Pamplona 2023-08-25 17:14:33 -04:00
parent a7d8c87c47
commit 7ef46a2662
15 changed files with 213 additions and 200 deletions

View File

@ -463,10 +463,6 @@ open class Note(val idHex: String) {
}.flatten()
}
fun countReactions(): Int {
return reactions.values.sumOf { it.size }
}
fun zappedAmount(privKey: ByteArray?, walletServicePubkey: ByteArray?): BigDecimal {
// Regular Zap Receipts
val completedZaps = zaps.asSequence()
@ -683,6 +679,10 @@ class NoteLiveSet(u: Note) {
it.note.replies.size
}.distinctUntilChanged()
val reactionCount = reactions.map {
it.note.reactions.values.sumOf { it.size }
}.distinctUntilChanged()
val boostCount = boosts.map {
it.note.boosts.size
}.distinctUntilChanged()

View File

@ -111,12 +111,14 @@ abstract class NostrDataSource(val debugName: String) {
private val bundler = BundledUpdate(300, Dispatchers.IO)
fun invalidateFilters() {
bundler.invalidate() {
// println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
scope.launch(Dispatchers.IO) {
bundler.invalidate() {
// println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
// adds the time to perform the refresh into this delay
// holding off new updates in case of heavy refresh routines.
resetFiltersSuspend()
// adds the time to perform the refresh into this delay
// holding off new updates in case of heavy refresh routines.
resetFiltersSuspend()
}
}
}

View File

@ -12,8 +12,6 @@ import com.vitorpamplona.amethyst.model.ConnectivityType
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun UrlPreview(url: String, urlText: String, accountViewModel: AccountViewModel) {
@ -37,10 +35,8 @@ fun UrlPreview(url: String, urlText: String, accountViewModel: AccountViewModel)
// Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created).
if (urlPreviewState == UrlPreviewState.Loading) {
LaunchedEffect(url) {
launch(Dispatchers.IO) {
UrlCachedPreviewer.previewInfo(url) {
urlPreviewState = it
}
accountViewModel.urlPreview(url) {
urlPreviewState = it
}
}
}

View File

@ -285,20 +285,16 @@ private fun EditStatusBox(baseAccountUser: User, accountViewModel: AccountViewMo
),
keyboardActions = KeyboardActions(
onSend = {
scope.launch(Dispatchers.IO) {
accountViewModel.createStatus(currentStatus.value)
focusManager.clearFocus(true)
}
accountViewModel.createStatus(currentStatus.value)
focusManager.clearFocus(true)
}
),
singleLine = true,
trailingIcon = {
if (hasChanged) {
UserStatusSendButton() {
scope.launch(Dispatchers.IO) {
accountViewModel.createStatus(currentStatus.value)
focusManager.clearFocus(true)
}
accountViewModel.createStatus(currentStatus.value)
focusManager.clearFocus(true)
}
}
}
@ -335,28 +331,21 @@ private fun EditStatusBox(baseAccountUser: User, accountViewModel: AccountViewMo
),
keyboardActions = KeyboardActions(
onSend = {
scope.launch(Dispatchers.IO) {
accountViewModel.updateStatus(it, thisStatus.value)
focusManager.clearFocus(true)
}
accountViewModel.updateStatus(it, thisStatus.value)
focusManager.clearFocus(true)
}
),
singleLine = true,
trailingIcon = {
if (hasChanged) {
UserStatusSendButton() {
scope.launch(Dispatchers.IO) {
accountViewModel.updateStatus(it, thisStatus.value)
focusManager.clearFocus(true)
}
accountViewModel.updateStatus(it, thisStatus.value)
focusManager.clearFocus(true)
}
} else {
UserStatusDeleteButton() {
scope.launch(Dispatchers.IO) {
accountViewModel.updateStatus(it, "")
accountViewModel.delete(it)
focusManager.clearFocus(true)
}
accountViewModel.deleteStatus(it)
focusManager.clearFocus(true)
}
}
}

View File

@ -42,7 +42,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.ImageUrlType
@ -52,7 +51,6 @@ import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.screen.CombinedZap
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.NotificationIconModifier
import com.vitorpamplona.amethyst.ui.theme.NotificationIconModifierSmaller
@ -70,8 +68,6 @@ import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import com.vitorpamplona.amethyst.ui.theme.overPictureBackground
import com.vitorpamplona.amethyst.ui.theme.profile35dpModifier
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
@ -352,8 +348,7 @@ private fun ParseAuthorCommentAndAmount(
}
LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) {
launch(Dispatchers.IO) {
val newState = loadAmountState(zapRequest, zapEvent, accountViewModel)
accountViewModel.decryptAmountMessage(zapRequest, zapEvent) { newState ->
if (newState != null) {
content.value = newState
}
@ -363,34 +358,6 @@ private fun ParseAuthorCommentAndAmount(
onReady(content)
}
private suspend fun loadAmountState(
zapRequest: Note,
zapEvent: Note?,
accountViewModel: AccountViewModel
): ZapAmountCommentNotification? {
(zapRequest.event as? LnZapRequestEvent)?.let {
val decryptedContent = accountViewModel.decryptZap(zapRequest)
val amount = (zapEvent?.event as? LnZapEvent)?.amount
if (decryptedContent != null) {
val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
return ZapAmountCommentNotification(
newAuthor,
decryptedContent.content.ifBlank { null },
showAmountAxis(amount)
)
} else {
if (!zapRequest.event?.content().isNullOrBlank() || amount != null) {
return ZapAmountCommentNotification(
zapRequest.author,
zapRequest.event?.content()?.ifBlank { null },
showAmountAxis(amount)
)
}
}
}
return null
}
@Composable
private fun RenderState(
content: MutableState<ZapAmountCommentNotification>,

View File

@ -98,9 +98,7 @@ private fun WatchZapsAndUpdateTallies(
val zapsState by baseNote.live().zaps.observeAsState()
LaunchedEffect(key1 = zapsState) {
launch(Dispatchers.Default) {
pollViewModel.refreshTallies()
}
pollViewModel.refreshTallies()
}
}

View File

@ -3,13 +3,16 @@ package com.vitorpamplona.amethyst.ui.note
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.events.*
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.math.RoundingMode
@ -52,12 +55,12 @@ class PollNoteViewModel : ViewModel() {
closedAt = pollEvent?.getTagInt(CLOSED_AT)
}
suspend fun refreshTallies() {
totalZapped = totalZapped()
wasZappedByLoggedInAccount = pollNote?.let { account?.calculateIfNoteWasZappedByAccount(it) } ?: false
fun refreshTallies() {
viewModelScope.launch(Dispatchers.Default) {
totalZapped = totalZapped()
wasZappedByLoggedInAccount = pollNote?.let { account?.calculateIfNoteWasZappedByAccount(it) } ?: false
_tallies.emit(
pollOptions?.keys?.map {
val newOptions = pollOptions?.keys?.map {
val zappedInOption = zappedPollOptionAmount(it)
val myTally = if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
@ -71,8 +74,12 @@ class PollNoteViewModel : ViewModel() {
val consensus = consensusThreshold != null && myTally >= consensusThreshold!!
PollOption(it, pollOptions?.get(it) ?: "", zappedInOption, myTally, consensus, zappedByLoggedIn)
} ?: emptyList()
)
}
_tallies.emit(
newOptions ?: emptyList()
)
}
}
fun canZap(): Boolean {

View File

@ -40,6 +40,7 @@ import androidx.compose.material.ProgressIndicatorDefaults
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
@ -241,7 +242,7 @@ private fun LoadAndDisplayZapraiser(
wantsToSeeReactions: MutableState<Boolean>,
accountViewModel: AccountViewModel
) {
val zapraiserAmount by remember {
val zapraiserAmount by remember(baseNote) {
derivedStateOf {
baseNote.event?.zapraiserAmount() ?: 0
}
@ -261,42 +262,26 @@ private fun LoadAndDisplayZapraiser(
}
}
@Immutable
data class ZapraiserStatus(val progress: Float, val left: String)
@Composable
fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, accountViewModel: AccountViewModel) {
val zapsState by baseNote.live().zaps.observeAsState()
var zapraiserProgress by remember { mutableStateOf(0F) }
var zapraiserLeft by remember { mutableStateOf("$zapraiserAmount") }
var zapraiserStatus by remember { mutableStateOf(ZapraiserStatus(0F, "$zapraiserAmount")) }
LaunchedEffect(key1 = zapsState) {
launch(Dispatchers.Default) {
zapsState?.note?.let {
val newZapAmount = accountViewModel.calculateZapAmount(it)
var percentage = newZapAmount.div(zapraiserAmount.toBigDecimal()).toFloat()
if (percentage > 1) {
percentage = 1f
}
if (Math.abs(zapraiserProgress - percentage) > 0.001) {
val newZapraiserProgress = percentage
val newZapraiserLeft = if (percentage > 0.99) {
"0"
} else {
showAmount((zapraiserAmount * (1 - percentage)).toBigDecimal())
}
if (zapraiserLeft != newZapraiserLeft) {
zapraiserLeft = newZapraiserLeft
}
if (zapraiserProgress != newZapraiserProgress) {
zapraiserProgress = newZapraiserProgress
}
zapsState?.note?.let {
accountViewModel.calculateZapraiser(baseNote) { newStatus ->
if (zapraiserStatus != newStatus) {
zapraiserStatus = newStatus
}
}
}
}
val color = if (zapraiserProgress > 0.99) {
val color = if (zapraiserStatus.progress > 0.99) {
DarkerGreen
} else {
MaterialTheme.colors.mediumImportanceLink
@ -307,7 +292,7 @@ fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, acc
.fillMaxWidth()
.height(if (details) 24.dp else 4.dp),
color = color,
progress = zapraiserProgress
progress = zapraiserStatus.progress
)
if (details) {
@ -315,14 +300,14 @@ fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, acc
contentAlignment = Center,
modifier = TinyBorders
) {
val totalPercentage by remember(zapraiserProgress) {
val totalPercentage by remember(zapraiserStatus) {
derivedStateOf {
"${(zapraiserProgress * 100).roundToInt()}%"
"${(zapraiserStatus.progress * 100).roundToInt()}%"
}
}
Text(
text = stringResource(id = R.string.sats_to_complete, totalPercentage, zapraiserLeft),
text = stringResource(id = R.string.sats_to_complete, totalPercentage, zapraiserStatus.left),
modifier = NoSoTinyBorders,
color = MaterialTheme.colors.placeholderText,
fontSize = Font14SP,
@ -836,10 +821,7 @@ private fun WatchReactionTypeForNote(baseNote: Note, accountViewModel: AccountVi
val reactionsState by baseNote.live().reactions.observeAsState()
LaunchedEffect(key1 = reactionsState) {
launch(Dispatchers.Default) {
val reactionNote = reactionsState?.note?.getReactionBy(accountViewModel.userProfile())
onNewReactionType(reactionNote)
}
accountViewModel.loadReactionTo(reactionsState?.note, onNewReactionType)
}
}
@ -874,32 +856,9 @@ private fun RenderReactionType(
@Composable
fun LikeText(baseNote: Note, grayTint: Color) {
val reactionsCount = remember(baseNote) {
mutableStateOf(baseNote.reactions.size)
}
val reactionCount by baseNote.live().reactionCount.observeAsState(0)
val scope = rememberCoroutineScope()
WatchReactionCountForNote(baseNote) { newReactionsCount ->
if (reactionsCount.value != newReactionsCount) {
scope.launch(Dispatchers.Main) {
reactionsCount.value = newReactionsCount
}
}
}
SlidingAnimationCount(reactionsCount, grayTint)
}
@Composable
private fun WatchReactionCountForNote(baseNote: Note, onNewReactionCount: (Int) -> Unit) {
val reactionsState by baseNote.live().reactions.observeAsState()
LaunchedEffect(key1 = reactionsState) {
launch(Dispatchers.Default) {
onNewReactionCount(reactionsState?.note?.countReactions() ?: 0)
}
}
SlidingAnimationCount(reactionCount, grayTint)
}
private fun likeClick(
@ -1139,9 +1098,7 @@ private fun WatchZapsForNote(baseNote: Note, accountViewModel: AccountViewModel,
val zapsState by baseNote.live().zaps.observeAsState()
LaunchedEffect(key1 = zapsState) {
launch(Dispatchers.Default) {
onWasZapped(accountViewModel.calculateIfNoteWasZappedByAccount(baseNote))
}
accountViewModel.calculateIfNoteWasZappedByAccount(baseNote, onWasZapped)
}
}
@ -1171,9 +1128,7 @@ fun WatchZapAmountsForNote(baseNote: Note, accountViewModel: AccountViewModel, o
val zapsState by baseNote.live().zaps.observeAsState()
LaunchedEffect(key1 = zapsState) {
launch(Dispatchers.Default) {
onZapAmount(showAmount(accountViewModel.calculateZapAmount(baseNote)))
}
accountViewModel.calculateZapAmount(baseNote, onZapAmount)
}
}

View File

@ -35,7 +35,6 @@ import com.vitorpamplona.amethyst.ui.note.MultiSetCompose
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.ZapUserSetCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
@ -102,11 +101,9 @@ private fun WatchScrollToTop(
val scrollToTop by viewModel.scrollToTop.collectAsState()
LaunchedEffect(scrollToTop) {
launch {
if (scrollToTop > 0 && viewModel.scrolltoTopPending) {
listState.scrollToItem(index = 0)
viewModel.sentToTop()
}
if (scrollToTop > 0 && viewModel.scrolltoTopPending) {
listState.scrollToItem(index = 0)
viewModel.sentToTop()
}
}
}

View File

@ -301,11 +301,13 @@ abstract class FeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
fun checkKeysInvalidateDataAndSendToTop() {
if (lastFeedKey != localFilter.feedKey()) {
bundler.invalidate(false) {
// adds the time to perform the refresh into this delay
// holding off new updates in case of heavy refresh routines.
refreshSuspended()
sendToTop()
viewModelScope.launch(Dispatchers.IO) {
bundler.invalidate(false) {
// adds the time to perform the refresh into this delay
// holding off new updates in case of heavy refresh routines.
refreshSuspended()
sendToTop()
}
}
}
}

View File

@ -16,14 +16,22 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountState
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.ConnectivityType
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.UserState
import com.vitorpamplona.amethyst.service.OnlineChecker
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.GiftWrapEvent
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.events.PayInvoiceErrorResponse
import com.vitorpamplona.quartz.events.ReportEvent
import com.vitorpamplona.quartz.events.SealedGossipEvent
@ -121,21 +129,86 @@ class AccountViewModel(val account: Account) : ViewModel() {
account.delete(account.boostsTo(note))
}
fun calculateIfNoteWasZappedByAccount(zappedNote: Note): Boolean {
return account.calculateIfNoteWasZappedByAccount(zappedNote)
fun calculateIfNoteWasZappedByAccount(zappedNote: Note, onWasZapped: (Boolean) -> Unit) {
viewModelScope.launch(Dispatchers.Default) {
onWasZapped(account.calculateIfNoteWasZappedByAccount(zappedNote))
}
}
fun calculateZapAmount(zappedNote: Note): BigDecimal {
suspend fun calculateZapAmount(zappedNote: Note): BigDecimal {
return account.calculateZappedAmount(zappedNote)
}
fun calculateZapAmount(zappedNote: Note, onZapAmount: (String) -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
onZapAmount(showAmount(account.calculateZappedAmount(zappedNote)))
}
}
fun calculateZapraiser(zappedNote: Note, onZapraiserStatus: (ZapraiserStatus) -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
val zapraiserAmount = zappedNote.event?.zapraiserAmount() ?: 0
val newZapAmount = calculateZapAmount(zappedNote)
var percentage = newZapAmount.div(zapraiserAmount.toBigDecimal()).toFloat()
if (percentage > 1) {
percentage = 1f
}
val newZapraiserProgress = percentage
val newZapraiserLeft = if (percentage > 0.99) {
"0"
} else {
showAmount((zapraiserAmount * (1 - percentage)).toBigDecimal())
}
onZapraiserStatus(ZapraiserStatus(newZapraiserProgress, newZapraiserLeft))
}
}
fun decryptAmountMessage(
zapRequest: Note,
zapEvent: Note?,
onNewState: (ZapAmountCommentNotification?) -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
onNewState(innerDecryptAmountMessage(zapRequest, zapEvent))
}
}
private suspend fun innerDecryptAmountMessage(
zapRequest: Note,
zapEvent: Note?
): ZapAmountCommentNotification? {
(zapRequest.event as? LnZapRequestEvent)?.let {
val decryptedContent = decryptZap(zapRequest)
val amount = (zapEvent?.event as? LnZapEvent)?.amount
if (decryptedContent != null) {
val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
return ZapAmountCommentNotification(
newAuthor,
decryptedContent.content.ifBlank { null },
showAmountAxis(amount)
)
} else {
if (!zapRequest.event?.content().isNullOrBlank() || amount != null) {
return ZapAmountCommentNotification(
zapRequest.author,
zapRequest.event?.content()?.ifBlank { null },
showAmountAxis(amount)
)
}
}
}
return null
}
fun zap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
viewModelScope.launch(Dispatchers.IO) {
innerZap(note, amount, pollOption, message, context, onError, onProgress, zapType)
}
}
suspend fun innerZap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
private suspend fun innerZap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
val lud16 = note.event?.zapAddress() ?: note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
if (lud16.isNullOrBlank()) {
@ -388,6 +461,26 @@ class AccountViewModel(val account: Account) : ViewModel() {
fun updateStatus(it: AddressableNote, newStatus: String) {
account.updateStatus(it, newStatus)
fun checkIfOnline(url: String, onResult: (Boolean) -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
val isOnline = OnlineChecker.isOnline(url)
onResult(isOnline)
}
}
fun urlPreview(url: String, onResult: suspend (UrlPreviewState) -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
UrlCachedPreviewer.previewInfo(url, onResult)
}
}
fun loadReactionTo(note: Note?, onNewReactionType: (String?) -> Unit) {
if (note == null) return
viewModelScope.launch(Dispatchers.Default) {
onNewReactionType(note.getReactionBy(userProfile()))
}
}
class Factory(val account: Account) : ViewModelProvider.Factory {

View File

@ -623,7 +623,7 @@ fun ShowVideoStreaming(
}
url?.let {
CheckIfUrlIsOnline(url) {
CheckIfUrlIsOnline(url, accountViewModel) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = remember { Modifier.heightIn(max = 300.dp) }

View File

@ -29,7 +29,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
import com.vitorpamplona.amethyst.service.OnlineChecker
import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
@ -42,7 +41,6 @@ import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@ -136,12 +134,14 @@ private fun HomePages(
}
@Composable
fun CheckIfUrlIsOnline(url: String, whenOnline: @Composable () -> Unit) {
fun CheckIfUrlIsOnline(url: String, accountViewModel: AccountViewModel, whenOnline: @Composable () -> Unit) {
var online by remember { mutableStateOf(false) }
LaunchedEffect(key1 = url) {
launch(Dispatchers.IO) {
online = OnlineChecker.isOnline(url)
accountViewModel.checkIfOnline(url) { isOnline ->
if (online != isOnline) {
online = isOnline
}
}
}
@ -162,11 +162,9 @@ fun WatchAccountForHomeScreen(
val followState by accountViewModel.account.userProfile().live().follows.observeAsState()
LaunchedEffect(accountViewModel, accountState?.account?.defaultHomeFollowList, followState) {
launch(Dispatchers.IO) {
NostrHomeDataSource.invalidateFilters()
homeFeedViewModel.checkKeysInvalidateDataAndSendToTop()
repliesFeedViewModel.checkKeysInvalidateDataAndSendToTop()
}
NostrHomeDataSource.invalidateFilters()
homeFeedViewModel.checkKeysInvalidateDataAndSendToTop()
repliesFeedViewModel.checkKeysInvalidateDataAndSendToTop()
}
}

View File

@ -73,42 +73,38 @@ fun LoadRedirectScreen(eventId: String?, accountViewModel: AccountViewModel, nav
fun LoadRedirectScreen(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val noteState by baseNote.live().metadata.observeAsState()
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = noteState) {
scope.launch {
val note = noteState?.note ?: return@launch
var event = note.event
val channelHex = note.channelHex()
val note = noteState?.note ?: return@LaunchedEffect
var event = note.event
val channelHex = note.channelHex()
if (event is GiftWrapEvent) {
event = accountViewModel.unwrap(event)
}
if (event is GiftWrapEvent) {
event = accountViewModel.unwrap(event)
}
if (event is SealedGossipEvent) {
event = accountViewModel.unseal(event)
}
if (event is SealedGossipEvent) {
event = accountViewModel.unseal(event)
}
if (event == null) {
// stay here, loading
} else if (event is ChannelCreateEvent) {
nav("Channel/${note.idHex}")
} else if (event is ChatroomKeyable) {
note.author?.let {
val withKey = (event as ChatroomKeyable)
.chatroomKey(accountViewModel.userProfile().pubkeyHex)
if (event == null) {
// stay here, loading
} else if (event is ChannelCreateEvent) {
nav("Channel/${note.idHex}")
} else if (event is ChatroomKeyable) {
note.author?.let {
val withKey = (event as ChatroomKeyable)
.chatroomKey(accountViewModel.userProfile().pubkeyHex)
withContext(Dispatchers.IO) {
accountViewModel.userProfile().createChatroom(withKey)
}
nav("Room/${withKey.hashCode()}")
withContext(Dispatchers.IO) {
accountViewModel.userProfile().createChatroom(withKey)
}
} else if (channelHex != null) {
nav("Channel/$channelHex")
} else {
nav("Note/${note.idHex}")
nav("Room/${withKey.hashCode()}")
}
} else if (channelHex != null) {
nav("Channel/$channelHex")
} else {
nav("Note/${note.idHex}")
}
}

View File

@ -50,5 +50,18 @@ class StatusEvent(
val sig = CryptoUtils.sign(id, privateKey)
return StatusEvent(id.toHexKey(), pubKey, createdAt, tags, newStatus, sig.toHexKey())
}
fun clear(
event: StatusEvent,
privateKey: ByteArray,
createdAt: Long = TimeUtils.now()
): StatusEvent {
val msg = ""
val tags = event.tags.filter { it.size > 1 && it[0] == "d" }
val pubKey = event.pubKey()
val id = generateId(pubKey, createdAt, kind, tags, msg)
val sig = CryptoUtils.sign(id, privateKey)
return StatusEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
}
}
}