mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-28 22:53:11 +02:00
- Makes zap calculation a sync routine
- Optimizes the display of the yellow zap when the zap event comes before the pay payment confirmation - Fixes animations for a new zap over an existing zap.
This commit is contained in:
@@ -516,10 +516,8 @@ class Account(
|
||||
|
||||
suspend fun calculateIfNoteWasZappedByAccount(
|
||||
zappedNote: Note?,
|
||||
onWasZapped: () -> Unit,
|
||||
) {
|
||||
zappedNote?.isZappedBy(userProfile(), this, onWasZapped)
|
||||
}
|
||||
afterTimeInSeconds: Long,
|
||||
): Boolean = zappedNote?.isZappedBy(userProfile(), afterTimeInSeconds, this) == true
|
||||
|
||||
suspend fun calculateZappedAmount(zappedNote: Note): BigDecimal = zappedNote.zappedAmountWithNWCPayments(nip47SignerState)
|
||||
|
||||
|
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import coil3.util.CoilUtils.result
|
||||
import com.vitorpamplona.amethyst.model.nip47WalletConnect.NwcSignerState
|
||||
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
@@ -472,28 +473,28 @@ open class Note(
|
||||
}
|
||||
|
||||
private suspend fun isPaidByCalculation(
|
||||
zapPayments: List<Pair<Note, Note?>>,
|
||||
afterTimeInSeconds: Long,
|
||||
account: Account,
|
||||
zapEvents: List<Pair<Note, Note?>>,
|
||||
onWasZappedByAuthor: () -> Unit,
|
||||
) {
|
||||
if (zapEvents.isEmpty()) {
|
||||
return
|
||||
): Boolean {
|
||||
if (zapPayments.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
var hasSentOne = false
|
||||
|
||||
launchAndWaitAll(zapEvents) { next ->
|
||||
return anyAsync(zapPayments) { next ->
|
||||
val zapResponseEvent = next.second?.event as? LnZapPaymentResponseEvent
|
||||
|
||||
if (zapResponseEvent != null) {
|
||||
account.nip47SignerState.decryptResponse(zapResponseEvent)?.let { response ->
|
||||
val result = response is PayInvoiceSuccessResponse && account.nip47SignerState.isNIP47Author(zapResponseEvent.requestAuthor())
|
||||
|
||||
if (!hasSentOne && result == true) {
|
||||
hasSentOne = true
|
||||
onWasZappedByAuthor()
|
||||
}
|
||||
val response = account.nip47SignerState.decryptResponse(zapResponseEvent)
|
||||
if (response != null) {
|
||||
response is PayInvoiceSuccessResponse &&
|
||||
account.nip47SignerState.isNIP47Author(zapResponseEvent.requestAuthor()) &&
|
||||
zapResponseEvent.createdAt > afterTimeInSeconds
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,75 +502,81 @@ open class Note(
|
||||
private suspend fun isZappedByCalculation(
|
||||
option: Int?,
|
||||
user: User,
|
||||
afterTimeInSeconds: Long,
|
||||
account: Account,
|
||||
zapEvents: Map<Note, Note?>,
|
||||
onWasZappedByAuthor: () -> Unit,
|
||||
) {
|
||||
): Boolean {
|
||||
if (zapEvents.isEmpty()) {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
val parallelDecrypt = mutableListOf<Pair<LnZapRequestEvent, LnZapEvent?>>()
|
||||
val parallelDecrypt = mutableListOf<Pair<LnZapRequestEvent, LnZapEvent>>()
|
||||
|
||||
zapEvents.forEach { next ->
|
||||
val zapRequest = next.key.event as LnZapRequestEvent
|
||||
val zapEvent = next.value?.event as? LnZapEvent
|
||||
|
||||
if (!zapRequest.isPrivateZap()) {
|
||||
// public events
|
||||
if (zapRequest.pubKey == user.pubkeyHex && (option == null || option == zapEvent?.zappedPollOption())) {
|
||||
onWasZappedByAuthor()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// private events
|
||||
|
||||
// if has already decrypted
|
||||
val privateZap = account.privateZapsDecryptionCache.cachedPrivateZap(zapRequest)
|
||||
if (privateZap != null) {
|
||||
if (privateZap.pubKey == user.pubkeyHex && (option == null || option == zapEvent?.zappedPollOption())) {
|
||||
onWasZappedByAuthor()
|
||||
return
|
||||
if (zapEvent != null) {
|
||||
if (!zapRequest.isPrivateZap()) {
|
||||
// public events
|
||||
if (zapRequest.pubKey == user.pubkeyHex &&
|
||||
zapEvent.createdAt > afterTimeInSeconds &&
|
||||
(option == null || option == zapEvent.zappedPollOption())
|
||||
) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (account.isWriteable()) {
|
||||
parallelDecrypt.add(Pair(zapRequest, zapEvent))
|
||||
// private events
|
||||
|
||||
// if has already decrypted
|
||||
val privateZap = account.privateZapsDecryptionCache.cachedPrivateZap(zapRequest)
|
||||
if (privateZap != null) {
|
||||
if (privateZap.pubKey == user.pubkeyHex &&
|
||||
zapEvent.createdAt > afterTimeInSeconds &&
|
||||
(option == null || option == zapEvent.zappedPollOption())
|
||||
) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (account.isWriteable()) {
|
||||
parallelDecrypt.add(Pair(zapRequest, zapEvent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result =
|
||||
anyAsync(parallelDecrypt) { pair ->
|
||||
val result = account.privateZapsDecryptionCache.decryptPrivateZap(pair.first)
|
||||
if (parallelDecrypt.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
result?.pubKey == user.pubkeyHex && (option == null || option == pair.second?.zappedPollOption())
|
||||
}
|
||||
|
||||
if (result) {
|
||||
onWasZappedByAuthor()
|
||||
return anyAsync(parallelDecrypt) { pair ->
|
||||
val result = account.privateZapsDecryptionCache.decryptPrivateZap(pair.first)
|
||||
result?.pubKey == user.pubkeyHex &&
|
||||
pair.second.createdAt > afterTimeInSeconds &&
|
||||
(option == null || option == pair.second.zappedPollOption())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun isZappedBy(
|
||||
user: User,
|
||||
afterTimeInSeconds: Long,
|
||||
account: Account,
|
||||
onWasZappedByAuthor: () -> Unit,
|
||||
) {
|
||||
isZappedByCalculation(null, user, account, zaps, onWasZappedByAuthor)
|
||||
): Boolean {
|
||||
val first = isZappedByCalculation(null, user, afterTimeInSeconds, account, zaps)
|
||||
if (first) return true
|
||||
if (account.userProfile() == user) {
|
||||
isPaidByCalculation(account, zapPayments.toList(), onWasZappedByAuthor)
|
||||
return isPaidByCalculation(zapPayments.toList(), afterTimeInSeconds, account)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
suspend fun isZappedBy(
|
||||
option: Int?,
|
||||
user: User,
|
||||
afterTimeInSeconds: Long,
|
||||
account: Account,
|
||||
onWasZappedByAuthor: () -> Unit,
|
||||
) {
|
||||
isZappedByCalculation(option, user, account, zaps, onWasZappedByAuthor)
|
||||
}
|
||||
): Boolean = isZappedByCalculation(option, user, afterTimeInSeconds, account, zaps)
|
||||
|
||||
fun getReactionBy(user: User): String? =
|
||||
reactions.firstNotNullOfOrNull {
|
||||
|
@@ -52,6 +52,7 @@ import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -90,6 +91,7 @@ import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size14Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
@@ -100,6 +102,7 @@ import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -517,6 +520,8 @@ fun ZapVote(
|
||||
}
|
||||
|
||||
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
||||
var zapStartingTime by remember { mutableLongStateOf(0L) }
|
||||
|
||||
var showErrorMessageDialog by remember { mutableStateOf<StringToastMsg?>(null) }
|
||||
|
||||
val context = LocalContext.current
|
||||
@@ -558,6 +563,7 @@ fun ZapVote(
|
||||
accountViewModel.zapAmountChoices().size == 1 &&
|
||||
pollViewModel.isValidInputVoteAmount(accountViewModel.zapAmountChoices().first())
|
||||
) {
|
||||
zapStartingTime = TimeUtils.now()
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
accountViewModel.zapAmountChoices().first() * 1000,
|
||||
@@ -583,6 +589,7 @@ fun ZapVote(
|
||||
accountViewModel,
|
||||
pollViewModel,
|
||||
poolOption.option,
|
||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||
onDismiss = {
|
||||
wantsToZap = false
|
||||
zappingProgress = 0f
|
||||
@@ -660,9 +667,10 @@ fun ZapVote(
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.width(3.dp))
|
||||
|
||||
CircularProgressIndicator(
|
||||
progress = { zappingProgress },
|
||||
modifier = Modifier.size(14.dp),
|
||||
modifier = Size14Modifier,
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
}
|
||||
@@ -688,6 +696,7 @@ fun FilteredZapAmountChoicePopup(
|
||||
accountViewModel: AccountViewModel,
|
||||
pollViewModel: PollNoteViewModel,
|
||||
pollOption: Int,
|
||||
onZapStarts: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
onChangeAmount: () -> Unit,
|
||||
onError: (title: String, text: String, toUser: User?) -> Unit,
|
||||
@@ -717,6 +726,7 @@ fun FilteredZapAmountChoicePopup(
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = {
|
||||
onZapStarts()
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
amountInSats * 1000,
|
||||
@@ -743,6 +753,7 @@ fun FilteredZapAmountChoicePopup(
|
||||
modifier =
|
||||
Modifier.combinedClickable(
|
||||
onClick = {
|
||||
onZapStarts()
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
amountInSats * 1000,
|
||||
|
@@ -103,10 +103,8 @@ class PollNoteViewModel : ViewModel() {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
totalZapped = totalZapped()
|
||||
wasZappedByLoggedInAccount = false
|
||||
account?.calculateIfNoteWasZappedByAccount(pollNote) {
|
||||
wasZappedByLoggedInAccount = true
|
||||
canZap.value = checkIfCanZap()
|
||||
}
|
||||
wasZappedByLoggedInAccount = account.calculateIfNoteWasZappedByAccount(pollNote, 0)
|
||||
canZap.value = checkIfCanZap()
|
||||
|
||||
tallies.forEach {
|
||||
val zappedValue = zappedPollOptionAmount(it.option)
|
||||
@@ -210,10 +208,8 @@ class PollNoteViewModel : ViewModel() {
|
||||
suspend fun isPollOptionZappedBy(
|
||||
option: Int,
|
||||
user: User,
|
||||
onWasZappedByAuthor: () -> Unit,
|
||||
) {
|
||||
pollNote?.isZappedBy(option, user, account!!, onWasZappedByAuthor)
|
||||
}
|
||||
afterTimeInSeconds: Long,
|
||||
): Boolean = pollNote?.isZappedBy(option, user, afterTimeInSeconds, account) == true
|
||||
|
||||
fun cachedIsPollOptionZappedBy(
|
||||
option: Int,
|
||||
|
@@ -66,6 +66,7 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -154,11 +155,13 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.reactionBox
|
||||
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.selectedReactionBoxModifier
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.EmptyClientListener.onError
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||
import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount
|
||||
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -994,6 +997,7 @@ fun ZapReaction(
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
||||
var zapStartingTime by remember { mutableLongStateOf(0L) }
|
||||
|
||||
Row(
|
||||
verticalAlignment = CenterVertically,
|
||||
@@ -1008,6 +1012,7 @@ fun ZapReaction(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
context,
|
||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||
onZappingProgress = { progress: Float -> scope.launch { zappingProgress = progress } },
|
||||
onMultipleChoices = {
|
||||
scope.launch {
|
||||
@@ -1033,6 +1038,7 @@ fun ZapReaction(
|
||||
baseNote = baseNote,
|
||||
popupYOffset = iconSize,
|
||||
accountViewModel = accountViewModel,
|
||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||
onDismiss = {
|
||||
wantsToZap = false
|
||||
zappingProgress = 0f
|
||||
@@ -1083,6 +1089,7 @@ fun ZapReaction(
|
||||
|
||||
if (wantsToSetCustomZap) {
|
||||
ZapCustomDialog(
|
||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||
onClose = { wantsToSetCustomZap = false },
|
||||
onError = { _, message, user ->
|
||||
scope.launch {
|
||||
@@ -1106,11 +1113,23 @@ fun ZapReaction(
|
||||
label = "ZapIconIndicator",
|
||||
)
|
||||
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
modifier = animationModifier,
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
ObserveZapIcon(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
zapStartingTime,
|
||||
) { wasZappedByLoggedInUser ->
|
||||
CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) {
|
||||
if (it) {
|
||||
ZappedIcon(iconSizeModifier)
|
||||
} else {
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
modifier = animationModifier,
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ObserveZapIcon(
|
||||
baseNote,
|
||||
@@ -1136,6 +1155,7 @@ fun zapClick(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
context: Context,
|
||||
onZapStarts: () -> Unit,
|
||||
onZappingProgress: (Float) -> Unit,
|
||||
onMultipleChoices: () -> Unit,
|
||||
onError: (String, String, User?) -> Unit,
|
||||
@@ -1162,6 +1182,7 @@ fun zapClick(
|
||||
R.string.login_with_a_private_key_to_be_able_to_send_zaps,
|
||||
)
|
||||
} else if (choices.size == 1) {
|
||||
onZapStarts()
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
choices.first() * 1000,
|
||||
@@ -1181,6 +1202,7 @@ fun zapClick(
|
||||
fun ObserveZapIcon(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
afterTimeInSeconds: Long = 0,
|
||||
inner: @Composable (MutableState<Boolean>) -> Unit,
|
||||
) {
|
||||
val wasZappedByLoggedInUser = remember { mutableStateOf(false) }
|
||||
@@ -1196,10 +1218,9 @@ fun ObserveZapIcon(
|
||||
|
||||
LaunchedEffect(key1 = zapsState) {
|
||||
if (zapsState?.note?.zapPayments?.isNotEmpty() == true || zapsState?.note?.zaps?.isNotEmpty() == true) {
|
||||
accountViewModel.calculateIfNoteWasZappedByAccount(baseNote) { newWasZapped ->
|
||||
if (wasZappedByLoggedInUser.value != newWasZapped) {
|
||||
wasZappedByLoggedInUser.value = newWasZapped
|
||||
}
|
||||
val newWasZapped = accountViewModel.calculateIfNoteWasZappedByAccount(baseNote, afterTimeInSeconds)
|
||||
if (wasZappedByLoggedInUser.value != newWasZapped) {
|
||||
wasZappedByLoggedInUser.value = newWasZapped
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1552,6 +1573,7 @@ fun ZapAmountChoicePopup(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
popupYOffset: Dp,
|
||||
onZapStarts: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
onChangeAmount: () -> Unit,
|
||||
onError: (title: String, text: String, user: User?) -> Unit,
|
||||
@@ -1562,7 +1584,7 @@ fun ZapAmountChoicePopup(
|
||||
accountViewModel.account.settings.syncedSettings.zaps.zapAmountChoices
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
ZapAmountChoicePopup(baseNote, zapAmountChoices, accountViewModel, popupYOffset, onDismiss, onChangeAmount, onError, onProgress, onPayViaIntent)
|
||||
ZapAmountChoicePopup(baseNote, zapAmountChoices, accountViewModel, popupYOffset, onZapStarts, onDismiss, onChangeAmount, onError, onProgress, onPayViaIntent)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -1571,6 +1593,7 @@ fun ZapAmountChoicePopup(
|
||||
zapAmountChoices: ImmutableList<Long>,
|
||||
accountViewModel: AccountViewModel,
|
||||
popupYOffset: Dp,
|
||||
onZapStarts: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
onChangeAmount: () -> Unit,
|
||||
onError: (title: String, text: String, user: User?) -> Unit,
|
||||
@@ -1578,7 +1601,7 @@ fun ZapAmountChoicePopup(
|
||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||
) {
|
||||
val visibilityState = rememberVisibilityState(onDismiss)
|
||||
ZapAmountChoicePopup(baseNote, zapAmountChoices, accountViewModel, popupYOffset, visibilityState, onChangeAmount, onError, onProgress, onPayViaIntent)
|
||||
ZapAmountChoicePopup(baseNote, zapAmountChoices, accountViewModel, popupYOffset, visibilityState, onZapStarts, onChangeAmount, onError, onProgress, onPayViaIntent)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||
@@ -1589,6 +1612,7 @@ fun ZapAmountChoicePopup(
|
||||
accountViewModel: AccountViewModel,
|
||||
popupYOffset: Dp,
|
||||
visibilityState: MutableTransitionState<Boolean>,
|
||||
onZapStarts: () -> Unit,
|
||||
onChangeAmount: () -> Unit,
|
||||
onError: (title: String, text: String, user: User?) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit,
|
||||
@@ -1615,6 +1639,7 @@ fun ZapAmountChoicePopup(
|
||||
Button(
|
||||
modifier = Modifier.padding(horizontal = 3.dp),
|
||||
onClick = {
|
||||
onZapStarts()
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
amountInSats * 1000,
|
||||
@@ -1641,6 +1666,7 @@ fun ZapAmountChoicePopup(
|
||||
modifier =
|
||||
Modifier.combinedClickable(
|
||||
onClick = {
|
||||
onZapStarts()
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
amountInSats * 1000,
|
||||
|
@@ -103,6 +103,7 @@ class ZapOptionViewModel : ViewModel() {
|
||||
|
||||
@Composable
|
||||
fun ZapCustomDialog(
|
||||
onZapStarts: () -> Unit,
|
||||
onClose: () -> Unit,
|
||||
onError: (title: String, text: String, user: User?) -> Unit,
|
||||
onProgress: (percent: Float) -> Unit,
|
||||
@@ -172,6 +173,7 @@ fun ZapCustomDialog(
|
||||
ZapButton(
|
||||
isActive = postViewModel.canSend() && !baseNote.isDraft(),
|
||||
) {
|
||||
onZapStarts()
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
postViewModel.value()!! * 1000L,
|
||||
|
@@ -40,6 +40,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -83,12 +84,14 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size14Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -285,7 +288,7 @@ fun ZapDonationButton(
|
||||
accountViewModel: AccountViewModel,
|
||||
iconSize: Dp = Size35dp,
|
||||
iconSizeModifier: Modifier = Size20Modifier,
|
||||
animationSize: Dp = 14.dp,
|
||||
animationModifier: Modifier = Size14Modifier,
|
||||
nav: INav,
|
||||
) {
|
||||
var wantsToZap by remember { mutableStateOf<ImmutableList<Long>?>(null) }
|
||||
@@ -300,6 +303,8 @@ fun ZapDonationButton(
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
||||
var zapStartingTime by remember { mutableLongStateOf(0L) }
|
||||
|
||||
var hasZapped by remember { mutableStateOf(false) }
|
||||
|
||||
Button(
|
||||
@@ -308,6 +313,7 @@ fun ZapDonationButton(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
context,
|
||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||
onZappingProgress = { progress: Float ->
|
||||
scope.launch { zappingProgress = progress }
|
||||
},
|
||||
@@ -329,6 +335,7 @@ fun ZapDonationButton(
|
||||
zapAmountChoices = it,
|
||||
popupYOffset = iconSize,
|
||||
accountViewModel = accountViewModel,
|
||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||
onDismiss = {
|
||||
wantsToZap = null
|
||||
zappingProgress = 0f
|
||||
@@ -376,17 +383,30 @@ fun ZapDonationButton(
|
||||
if (zappingProgress > 0.00001 && zappingProgress < 0.99999) {
|
||||
Spacer(ModifierWidth3dp)
|
||||
|
||||
CircularProgressIndicator(
|
||||
progress =
|
||||
animateFloatAsState(
|
||||
targetValue = zappingProgress,
|
||||
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||
label = "ZapIconIndicator",
|
||||
).value,
|
||||
modifier = remember { Modifier.size(animationSize) },
|
||||
strokeWidth = 2.dp,
|
||||
color = grayTint,
|
||||
val animatedProgress by animateFloatAsState(
|
||||
targetValue = zappingProgress,
|
||||
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||
label = "ZapIconIndicator",
|
||||
)
|
||||
|
||||
ObserveZapIcon(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
zapStartingTime,
|
||||
) { wasZappedByLoggedInUser ->
|
||||
CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) {
|
||||
if (it) {
|
||||
ZappedIcon(iconSizeModifier)
|
||||
} else {
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
modifier = animationModifier,
|
||||
strokeWidth = 2.dp,
|
||||
color = grayTint,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ObserveZapIcon(
|
||||
baseNote,
|
||||
@@ -423,6 +443,7 @@ fun customZapClick(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
context: Context,
|
||||
onZapStarts: () -> Unit,
|
||||
onZappingProgress: (Float) -> Unit,
|
||||
onMultipleChoices: (List<Long>) -> Unit,
|
||||
onError: (String, String, User?) -> Unit,
|
||||
@@ -452,6 +473,7 @@ fun customZapClick(
|
||||
val amount = choices.first()
|
||||
|
||||
if (amount > 1100) {
|
||||
onZapStarts()
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
amount * 1000,
|
||||
|
@@ -395,12 +395,11 @@ class AccountViewModel(
|
||||
|
||||
suspend fun calculateIfNoteWasZappedByAccount(
|
||||
zappedNote: Note,
|
||||
onWasZapped: (Boolean) -> Unit,
|
||||
) {
|
||||
afterTimeInSeconds: Long,
|
||||
): Boolean =
|
||||
withContext(Dispatchers.IO) {
|
||||
account.calculateIfNoteWasZappedByAccount(zappedNote) { onWasZapped(true) }
|
||||
account.calculateIfNoteWasZappedByAccount(zappedNote, afterTimeInSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun calculateZapAmount(zappedNote: Note): String =
|
||||
if (zappedNote.zapPayments.isNotEmpty()) {
|
||||
|
@@ -40,6 +40,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -97,6 +98,7 @@ import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppMetadata
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -472,6 +474,7 @@ fun ZapDVMButton(
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
||||
var zapStartingTime by remember { mutableLongStateOf(0L) }
|
||||
var hasZapped by remember { mutableStateOf(false) }
|
||||
|
||||
Button(
|
||||
@@ -480,6 +483,7 @@ fun ZapDVMButton(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
context,
|
||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||
onZappingProgress = { progress: Float ->
|
||||
scope.launch { zappingProgress = progress }
|
||||
},
|
||||
@@ -501,6 +505,7 @@ fun ZapDVMButton(
|
||||
zapAmountChoices = persistentListOf(amount / 1000),
|
||||
popupYOffset = iconSize,
|
||||
accountViewModel = accountViewModel,
|
||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||
onDismiss = {
|
||||
wantsToZap = null
|
||||
zappingProgress = 0f
|
||||
@@ -554,12 +559,24 @@ fun ZapDVMButton(
|
||||
label = "ZapIconIndicator",
|
||||
)
|
||||
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
modifier = remember { Modifier.size(animationSize) },
|
||||
strokeWidth = 2.dp,
|
||||
color = grayTint,
|
||||
)
|
||||
ObserveZapIcon(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
zapStartingTime,
|
||||
) { wasZappedByLoggedInUser ->
|
||||
CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) {
|
||||
if (it) {
|
||||
ZappedIcon(iconSizeModifier)
|
||||
} else {
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
modifier = remember { Modifier.size(animationSize) },
|
||||
strokeWidth = 2.dp,
|
||||
color = grayTint,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ObserveZapIcon(
|
||||
baseNote,
|
||||
|
Reference in New Issue
Block a user