Created ReusableZapButton with common zap button logic
ZapDVMButton refactored to use ReusableZapButton with DVM-specific configuration
This commit is contained in:
@@ -0,0 +1,327 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssemblerSubscription
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size14Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for zap button behavior and appearance
|
||||||
|
*/
|
||||||
|
data class ZapButtonConfig(
|
||||||
|
val grayTint: Color = Color.Gray,
|
||||||
|
val iconSize: Dp = Size35dp,
|
||||||
|
val iconSizeModifier: Modifier = Size20Modifier,
|
||||||
|
val animationModifier: Modifier = Size14Modifier,
|
||||||
|
val showUserFinderSubscription: Boolean = false,
|
||||||
|
val zapAmountChoices: List<Long>? = null,
|
||||||
|
val thankYouText: String? = null,
|
||||||
|
val buttonText: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks for zap button events
|
||||||
|
*/
|
||||||
|
data class ZapButtonCallbacks(
|
||||||
|
val onZapComplete: ((Boolean) -> Unit)? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReusableZapButton(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
config: ZapButtonConfig = ZapButtonConfig(),
|
||||||
|
callbacks: ZapButtonCallbacks = ZapButtonCallbacks(),
|
||||||
|
) {
|
||||||
|
var wantsToZap by remember { mutableStateOf<ImmutableList<Long>?>(null) }
|
||||||
|
var wantsToPay by remember(baseNote) {
|
||||||
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(persistentListOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes sure the user is loaded to get his ln address ahead of time (for DVM buttons)
|
||||||
|
if (config.showUserFinderSubscription) {
|
||||||
|
baseNote.author?.let { author ->
|
||||||
|
UserFinderFilterAssemblerSubscription(author, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
||||||
|
var zapStartingTime by remember { mutableLongStateOf(0L) }
|
||||||
|
var hasZapped by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
handleZapClick(
|
||||||
|
baseNote = baseNote,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
context = context,
|
||||||
|
zapAmountChoices = config.zapAmountChoices,
|
||||||
|
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||||
|
onZappingProgress = { progress ->
|
||||||
|
scope.launch { zappingProgress = progress }
|
||||||
|
},
|
||||||
|
onMultipleChoices = { options ->
|
||||||
|
wantsToZap = options.toImmutableList()
|
||||||
|
},
|
||||||
|
onError = { _, message, toUser ->
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, toUser)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPayViaIntent = { wantsToPay = it },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
wantsToZap?.let { zapAmountChoices ->
|
||||||
|
ZapAmountChoicePopup(
|
||||||
|
baseNote = baseNote,
|
||||||
|
zapAmountChoices = zapAmountChoices,
|
||||||
|
popupYOffset = config.iconSize,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
||||||
|
onDismiss = {
|
||||||
|
wantsToZap = null
|
||||||
|
zappingProgress = 0f
|
||||||
|
},
|
||||||
|
onChangeAmount = {
|
||||||
|
wantsToZap = null
|
||||||
|
},
|
||||||
|
onError = { _, message, user ->
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onProgress = {
|
||||||
|
scope.launch(Dispatchers.Main) { zappingProgress = it }
|
||||||
|
},
|
||||||
|
onPayViaIntent = { wantsToPay = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantsToPay.isNotEmpty()) {
|
||||||
|
PayViaIntentDialog(
|
||||||
|
payingInvoices = wantsToPay,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
onClose = { wantsToPay = persistentListOf() },
|
||||||
|
onError = {
|
||||||
|
wantsToPay = persistentListOf()
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
justShowError = {
|
||||||
|
scope.launch {
|
||||||
|
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zap Icon and Progress
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = config.iconSizeModifier,
|
||||||
|
) {
|
||||||
|
if (zappingProgress > 0.00001 && zappingProgress < 0.99999) {
|
||||||
|
Spacer(ModifierWidth3dp)
|
||||||
|
|
||||||
|
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(config.iconSizeModifier)
|
||||||
|
} else {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
progress = { animatedProgress },
|
||||||
|
modifier = config.animationModifier,
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
color = config.grayTint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ObserveZapIcon(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
) { wasZappedByLoggedInUser ->
|
||||||
|
LaunchedEffect(wasZappedByLoggedInUser.value) {
|
||||||
|
hasZapped = wasZappedByLoggedInUser.value
|
||||||
|
callbacks.onZapComplete?.invoke(wasZappedByLoggedInUser.value)
|
||||||
|
|
||||||
|
if (wasZappedByLoggedInUser.value && !accountViewModel.account.hasDonatedInThisVersion()) {
|
||||||
|
delay(1000)
|
||||||
|
accountViewModel.markDonatedInThisVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CrossfadeIfEnabled(
|
||||||
|
targetState = wasZappedByLoggedInUser.value,
|
||||||
|
label = "ZapIcon",
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
) {
|
||||||
|
if (it) {
|
||||||
|
ZappedIcon(config.iconSizeModifier)
|
||||||
|
} else {
|
||||||
|
ZapIcon(config.iconSizeModifier, config.grayTint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val displayText =
|
||||||
|
when {
|
||||||
|
hasZapped -> config.thankYouText ?: stringRes(id = R.string.thank_you)
|
||||||
|
config.buttonText != null -> config.buttonText
|
||||||
|
else -> stringRes(id = R.string.donate_now)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = displayText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleZapClick(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
context: Context,
|
||||||
|
zapAmountChoices: List<Long>?,
|
||||||
|
onZapStarts: () -> Unit,
|
||||||
|
onZappingProgress: (Float) -> Unit,
|
||||||
|
onMultipleChoices: (List<Long>) -> Unit,
|
||||||
|
onError: (String, String, User?) -> Unit,
|
||||||
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
|
) {
|
||||||
|
if (baseNote.isDraft()) {
|
||||||
|
accountViewModel.toastManager.toast(
|
||||||
|
R.string.draft_note,
|
||||||
|
R.string.it_s_not_possible_to_zap_to_a_draft_note,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val choices = zapAmountChoices ?: accountViewModel.zapAmountChoices()
|
||||||
|
|
||||||
|
if (choices.isEmpty()) {
|
||||||
|
accountViewModel.toastManager.toast(
|
||||||
|
stringRes(context, R.string.error_dialog_zap_error),
|
||||||
|
stringRes(context, R.string.no_zap_amount_setup_long_press_to_change),
|
||||||
|
)
|
||||||
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
|
accountViewModel.toastManager.toast(
|
||||||
|
stringRes(context, R.string.error_dialog_zap_error),
|
||||||
|
stringRes(context, R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
||||||
|
)
|
||||||
|
} else if (choices.size == 1) {
|
||||||
|
val amount = choices.first()
|
||||||
|
|
||||||
|
if (amount > 1100 || zapAmountChoices != null) {
|
||||||
|
onZapStarts()
|
||||||
|
accountViewModel.zap(
|
||||||
|
baseNote,
|
||||||
|
amount * 1000,
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
context,
|
||||||
|
showErrorIfNoLnAddress = false,
|
||||||
|
onError = onError,
|
||||||
|
onProgress = { onZappingProgress(it) },
|
||||||
|
onPayViaIntent = onPayViaIntent,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
onMultipleChoices(listOf(1000L, 5_000L, 10_000L))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (choices.any { it > 1100 } || zapAmountChoices != null) {
|
||||||
|
onMultipleChoices(choices)
|
||||||
|
} else {
|
||||||
|
onMultipleChoices(listOf(1000L, 5_000L, 10_000L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-239
@@ -20,84 +20,54 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.elements
|
package com.vitorpamplona.amethyst.ui.note.elements
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
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.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.LinkAnnotation
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.withLink
|
import androidx.compose.ui.text.withLink
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.ReusableZapButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.ZapButtonConfig
|
||||||
import com.vitorpamplona.amethyst.ui.components.appendLink
|
import com.vitorpamplona.amethyst.ui.components.appendLink
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.DisplayZapSplits
|
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.DisplayZapSplits
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
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.Size10dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size14Modifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
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.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
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
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@@ -286,214 +256,17 @@ fun ZapDonationButton(
|
|||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
grayTint: Color,
|
grayTint: Color,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
iconSize: Dp = Size35dp,
|
|
||||||
iconSizeModifier: Modifier = Size20Modifier,
|
|
||||||
animationModifier: Modifier = Size14Modifier,
|
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
var wantsToZap by remember { mutableStateOf<ImmutableList<Long>?>(null) }
|
val config =
|
||||||
var wantsToPay by
|
ZapButtonConfig(
|
||||||
remember(baseNote) {
|
grayTint = grayTint,
|
||||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
)
|
||||||
persistentListOf(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
ReusableZapButton(
|
||||||
val scope = rememberCoroutineScope()
|
baseNote = baseNote,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
nav = nav,
|
||||||
var zapStartingTime by remember { mutableLongStateOf(0L) }
|
config = config,
|
||||||
|
)
|
||||||
var hasZapped by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
customZapClick(
|
|
||||||
baseNote,
|
|
||||||
accountViewModel,
|
|
||||||
context,
|
|
||||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
|
||||||
onZappingProgress = { progress: Float ->
|
|
||||||
scope.launch { zappingProgress = progress }
|
|
||||||
},
|
|
||||||
onMultipleChoices = { options -> wantsToZap = options.toImmutableList() },
|
|
||||||
onError = { _, message, toUser ->
|
|
||||||
scope.launch {
|
|
||||||
zappingProgress = 0f
|
|
||||||
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, toUser)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPayViaIntent = { wantsToPay = it },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
wantsToZap?.let {
|
|
||||||
ZapAmountChoicePopup(
|
|
||||||
baseNote = baseNote,
|
|
||||||
zapAmountChoices = it,
|
|
||||||
popupYOffset = iconSize,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
|
||||||
onDismiss = {
|
|
||||||
wantsToZap = null
|
|
||||||
zappingProgress = 0f
|
|
||||||
},
|
|
||||||
onChangeAmount = {
|
|
||||||
wantsToZap = null
|
|
||||||
},
|
|
||||||
onError = { _, message, user ->
|
|
||||||
scope.launch {
|
|
||||||
zappingProgress = 0f
|
|
||||||
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onProgress = {
|
|
||||||
scope.launch(Dispatchers.Main) { zappingProgress = it }
|
|
||||||
},
|
|
||||||
onPayViaIntent = { wantsToPay = it },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wantsToPay.isNotEmpty()) {
|
|
||||||
PayViaIntentDialog(
|
|
||||||
payingInvoices = wantsToPay,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
onClose = { wantsToPay = persistentListOf() },
|
|
||||||
onError = {
|
|
||||||
wantsToPay = persistentListOf()
|
|
||||||
scope.launch {
|
|
||||||
zappingProgress = 0f
|
|
||||||
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
justShowError = {
|
|
||||||
scope.launch {
|
|
||||||
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = iconSizeModifier,
|
|
||||||
) {
|
|
||||||
if (zappingProgress > 0.00001 && zappingProgress < 0.99999) {
|
|
||||||
Spacer(ModifierWidth3dp)
|
|
||||||
|
|
||||||
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,
|
|
||||||
accountViewModel,
|
|
||||||
) { wasZappedByLoggedInUser ->
|
|
||||||
LaunchedEffect(wasZappedByLoggedInUser.value) {
|
|
||||||
hasZapped = wasZappedByLoggedInUser.value
|
|
||||||
if (wasZappedByLoggedInUser.value && !accountViewModel.account.hasDonatedInThisVersion()) {
|
|
||||||
delay(1000)
|
|
||||||
accountViewModel.markDonatedInThisVersion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) {
|
|
||||||
if (it) {
|
|
||||||
ZappedIcon(iconSizeModifier)
|
|
||||||
} else {
|
|
||||||
ZapIcon(iconSizeModifier, grayTint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasZapped) {
|
|
||||||
Text(text = stringRes(id = R.string.thank_you))
|
|
||||||
} else {
|
|
||||||
Text(text = stringRes(id = R.string.donate_now))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun customZapClick(
|
|
||||||
baseNote: Note,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
context: Context,
|
|
||||||
onZapStarts: () -> Unit,
|
|
||||||
onZappingProgress: (Float) -> Unit,
|
|
||||||
onMultipleChoices: (List<Long>) -> Unit,
|
|
||||||
onError: (String, String, User?) -> Unit,
|
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
|
||||||
) {
|
|
||||||
if (baseNote.isDraft()) {
|
|
||||||
accountViewModel.toastManager.toast(
|
|
||||||
R.string.draft_note,
|
|
||||||
R.string.it_s_not_possible_to_zap_to_a_draft_note,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val choices = accountViewModel.zapAmountChoices()
|
|
||||||
|
|
||||||
if (choices.isEmpty()) {
|
|
||||||
accountViewModel.toastManager.toast(
|
|
||||||
stringRes(context, R.string.error_dialog_zap_error),
|
|
||||||
stringRes(context, R.string.no_zap_amount_setup_long_press_to_change),
|
|
||||||
)
|
|
||||||
} else if (!accountViewModel.isWriteable()) {
|
|
||||||
accountViewModel.toastManager.toast(
|
|
||||||
stringRes(context, R.string.error_dialog_zap_error),
|
|
||||||
stringRes(context, R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
|
||||||
)
|
|
||||||
} else if (choices.size == 1) {
|
|
||||||
val amount = choices.first()
|
|
||||||
|
|
||||||
if (amount > 1100) {
|
|
||||||
onZapStarts()
|
|
||||||
accountViewModel.zap(
|
|
||||||
baseNote,
|
|
||||||
amount * 1000,
|
|
||||||
null,
|
|
||||||
"",
|
|
||||||
context,
|
|
||||||
showErrorIfNoLnAddress = false,
|
|
||||||
onError = onError,
|
|
||||||
onProgress = { onZappingProgress(it) },
|
|
||||||
onPayViaIntent = onPayViaIntent,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
onMultipleChoices(listOf(1000L, 5_000L, 10_000L))
|
|
||||||
// recommends amounts for a monthly release.
|
|
||||||
}
|
|
||||||
} else if (choices.size > 1) {
|
|
||||||
if (choices.any { it > 1100 }) {
|
|
||||||
onMultipleChoices(choices)
|
|
||||||
} else {
|
|
||||||
onMultipleChoices(listOf(1000L, 5_000L, 10_000L))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-177
@@ -20,30 +20,21 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.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.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableLongStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Alignment.Companion.BottomStart
|
import androidx.compose.ui.Alignment.Companion.BottomStart
|
||||||
@@ -61,25 +52,18 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
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.ZapPaymentHandler
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteAndMap
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteAndMap
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssemblerSubscription
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.ReusableZapButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.ZapButtonConfig
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
|
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
|
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
|
||||||
import com.vitorpamplona.amethyst.ui.note.elements.customZapClick
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RenderFeedState
|
import com.vitorpamplona.amethyst.ui.screen.RenderFeedState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
|
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
|
||||||
@@ -88,9 +72,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.DVMCard
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.dal.NIP90ContentDiscoveryFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.dal.NIP90ContentDiscoveryFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.SimpleImage75Modifier
|
import com.vitorpamplona.amethyst.ui.theme.SimpleImage75Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||||
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
|
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceErrorResponse
|
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceErrorResponse
|
||||||
@@ -98,12 +80,6 @@ import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
|||||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppMetadata
|
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppMetadata
|
||||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
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
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DvmContentDiscoveryScreen(
|
fun DvmContentDiscoveryScreen(
|
||||||
@@ -453,160 +429,23 @@ fun ZapDVMButton(
|
|||||||
grayTint: Color,
|
grayTint: Color,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
iconSize: Dp = Size35dp,
|
iconSize: Dp = Size35dp,
|
||||||
iconSizeModifier: Modifier = Size20Modifier,
|
|
||||||
animationSize: Dp = 14.dp,
|
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val noteAuthor = baseNote.author ?: return
|
val config =
|
||||||
|
ZapButtonConfig(
|
||||||
|
grayTint = grayTint,
|
||||||
|
iconSize = iconSize,
|
||||||
|
showUserFinderSubscription = true,
|
||||||
|
zapAmountChoices = listOf(amount / 1000),
|
||||||
|
buttonText = "Zap ${(amount / 1000)} sats to the DVM",
|
||||||
|
)
|
||||||
|
|
||||||
var wantsToZap by remember { mutableStateOf<List<Long>?>(null) }
|
ReusableZapButton(
|
||||||
var wantsToPay by
|
baseNote = baseNote,
|
||||||
remember(baseNote) {
|
accountViewModel = accountViewModel,
|
||||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
nav = nav,
|
||||||
persistentListOf(),
|
config = config,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Makes sure the user is loaded to get his ln address ahead of time.
|
|
||||||
UserFinderFilterAssemblerSubscription(noteAuthor, accountViewModel)
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
|
||||||
var zapStartingTime by remember { mutableLongStateOf(0L) }
|
|
||||||
var hasZapped by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
customZapClick(
|
|
||||||
baseNote,
|
|
||||||
accountViewModel,
|
|
||||||
context,
|
|
||||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
|
||||||
onZappingProgress = { progress: Float ->
|
|
||||||
scope.launch { zappingProgress = progress }
|
|
||||||
},
|
|
||||||
onMultipleChoices = { options -> wantsToZap = options },
|
|
||||||
onError = { _, message, toUser ->
|
|
||||||
scope.launch {
|
|
||||||
zappingProgress = 0f
|
|
||||||
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, toUser)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPayViaIntent = { wantsToPay = it },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
if (wantsToZap != null) {
|
|
||||||
ZapAmountChoicePopup(
|
|
||||||
baseNote = baseNote,
|
|
||||||
zapAmountChoices = persistentListOf(amount / 1000),
|
|
||||||
popupYOffset = iconSize,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
onZapStarts = { zapStartingTime = TimeUtils.now() },
|
|
||||||
onDismiss = {
|
|
||||||
wantsToZap = null
|
|
||||||
zappingProgress = 0f
|
|
||||||
},
|
|
||||||
onChangeAmount = {
|
|
||||||
wantsToZap = null
|
|
||||||
},
|
|
||||||
onError = { _, message, user ->
|
|
||||||
scope.launch {
|
|
||||||
zappingProgress = 0f
|
|
||||||
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onProgress = {
|
|
||||||
scope.launch(Dispatchers.Main) { zappingProgress = it }
|
|
||||||
},
|
|
||||||
onPayViaIntent = { wantsToPay = it },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wantsToPay.isNotEmpty()) {
|
|
||||||
PayViaIntentDialog(
|
|
||||||
payingInvoices = wantsToPay,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
onClose = { wantsToPay = persistentListOf() },
|
|
||||||
onError = {
|
|
||||||
wantsToPay = persistentListOf()
|
|
||||||
scope.launch {
|
|
||||||
zappingProgress = 0f
|
|
||||||
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
justShowError = {
|
|
||||||
scope.launch {
|
|
||||||
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = iconSizeModifier,
|
|
||||||
) {
|
|
||||||
if (zappingProgress > 0.00001 && zappingProgress < 0.99999) {
|
|
||||||
Spacer(ModifierWidth3dp)
|
|
||||||
|
|
||||||
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 = remember { Modifier.size(animationSize) },
|
|
||||||
strokeWidth = 2.dp,
|
|
||||||
color = grayTint,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ObserveZapIcon(
|
|
||||||
baseNote,
|
|
||||||
accountViewModel,
|
|
||||||
) { wasZappedByLoggedInUser ->
|
|
||||||
LaunchedEffect(wasZappedByLoggedInUser.value) {
|
|
||||||
hasZapped = wasZappedByLoggedInUser.value
|
|
||||||
if (wasZappedByLoggedInUser.value && !accountViewModel.account.hasDonatedInThisVersion()) {
|
|
||||||
delay(1000)
|
|
||||||
accountViewModel.markDonatedInThisVersion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CrossfadeIfEnabled(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon", accountViewModel = accountViewModel) {
|
|
||||||
if (it) {
|
|
||||||
ZappedIcon(iconSizeModifier)
|
|
||||||
} else {
|
|
||||||
ZapIcon(iconSizeModifier, grayTint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasZapped) {
|
|
||||||
Text(text = stringRes(id = R.string.thank_you))
|
|
||||||
} else {
|
|
||||||
Text(text = "Zap " + (amount / 1000).toString() + " sats to the DVM") // stringRes(id = R.string.donate_now))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
Reference in New Issue
Block a user