mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-10-10 22:24:50 +02:00
add possibility to zap/pay invoices for dvms
This commit is contained in:
@@ -20,28 +20,43 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
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.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.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
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.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
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.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
@@ -51,21 +66,41 @@ import coil.compose.AsyncImage
|
|||||||
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.ui.components.LoadNote
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||||
import com.vitorpamplona.amethyst.ui.note.DVMCard
|
import com.vitorpamplona.amethyst.ui.note.DVMCard
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.LoadUser
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||||
|
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.customZapClick
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.screen.FeedEmpty
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.screen.RefresheableBox
|
||||||
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
|
||||||
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.QuoteBorder
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
||||||
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
||||||
|
import com.vitorpamplona.quartz.events.Event
|
||||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
||||||
import com.vitorpamplona.quartz.events.NIP90StatusEvent
|
import com.vitorpamplona.quartz.events.NIP90StatusEvent
|
||||||
|
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 NIP90ContentDiscoveryScreen(
|
fun NIP90ContentDiscoveryScreen(
|
||||||
@@ -81,7 +116,7 @@ fun NIP90ContentDiscoveryScreen(
|
|||||||
NIP90ContentDiscoveryScreen(baseNote, accountViewModel, nav)
|
NIP90ContentDiscoveryScreen(baseNote, accountViewModel, nav)
|
||||||
},
|
},
|
||||||
onBlank = {
|
onBlank = {
|
||||||
FeedEmptywithStatus(baseNote, stringResource(R.string.dvm_looking_for_app), accountViewModel, nav)
|
FeedDVM(baseNote, null, accountViewModel, nav)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -125,7 +160,7 @@ fun NIP90ContentDiscoveryScreen(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// TODO: Make a good splash screen with loading animation for this DVM.
|
// TODO: Make a good splash screen with loading animation for this DVM.
|
||||||
FeedEmptywithStatus(appDefinition, stringResource(R.string.dvm_requesting_job), accountViewModel, nav)
|
FeedDVM(appDefinition, null, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,16 +214,8 @@ fun ObserverDvmStatusResponse(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val latestStatus by statusFlow.collectAsStateWithLifecycle()
|
val latestStatus by statusFlow.collectAsStateWithLifecycle()
|
||||||
|
// TODO: Make a good splash screen with loading animation for this DVM.
|
||||||
if (latestStatus != null) {
|
FeedDVM(appDefinition, latestStatus, accountViewModel, nav)
|
||||||
// TODO: Make a good splash screen with loading animation for this DVM.
|
|
||||||
latestStatus?.let {
|
|
||||||
FeedEmptywithStatus(appDefinition, it.content(), accountViewModel, nav)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: Make a good splash screen with loading animation for this DVM.
|
|
||||||
FeedEmptywithStatus(appDefinition, stringResource(R.string.dvm_waiting_status), accountViewModel, nav)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -220,9 +247,6 @@ fun RenderNostrNIP90ContentDiscoveryScreen(
|
|||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
// TODO (Optional) Maybe render a nice header with image and DVM name from the dvmID
|
|
||||||
// TODO (Optional) How do we get the event information here?, LocalCache.checkGetOrCreateNote() returns note but event is empty
|
|
||||||
// TODO (Optional) otherwise we have the NIP89 info in (note.event as AppDefinitionEvent).appMetaData()
|
|
||||||
SaveableFeedState(resultFeedViewModel, null) { listState ->
|
SaveableFeedState(resultFeedViewModel, null) { listState ->
|
||||||
// TODO (Optional) Instead of a like reaction, do a Kind 31989 NIP89 App recommendation
|
// TODO (Optional) Instead of a like reaction, do a Kind 31989 NIP89 App recommendation
|
||||||
RenderFeedState(
|
RenderFeedState(
|
||||||
@@ -232,7 +256,6 @@ fun RenderNostrNIP90ContentDiscoveryScreen(
|
|||||||
nav,
|
nav,
|
||||||
null,
|
null,
|
||||||
onEmpty = {
|
onEmpty = {
|
||||||
// TODO (Optional) Maybe also show some dvm image/text while waiting for the notes in this custom component
|
|
||||||
FeedEmpty {
|
FeedEmpty {
|
||||||
onRefresh()
|
onRefresh()
|
||||||
}
|
}
|
||||||
@@ -242,13 +265,58 @@ fun RenderNostrNIP90ContentDiscoveryScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FeedEmptywithStatus(
|
fun FeedDVM(
|
||||||
appDefinitionNote: Note,
|
appDefinitionNote: Note,
|
||||||
status: String,
|
latestStatus: Event?,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
var status = "waiting"
|
||||||
|
var content = ""
|
||||||
|
var lninvoice = ""
|
||||||
|
var amount: Long = 0
|
||||||
|
var statusNote: Note? = null
|
||||||
|
var dvmUser: User? = null
|
||||||
|
|
||||||
|
if (latestStatus == null) {
|
||||||
|
content = stringResource(R.string.dvm_waiting_status)
|
||||||
|
} else {
|
||||||
|
latestStatus.let {
|
||||||
|
LoadNote(baseNoteHex = it.id, accountViewModel = accountViewModel) { stateNote ->
|
||||||
|
if (stateNote != null) {
|
||||||
|
statusNote = stateNote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content = it.content()
|
||||||
|
|
||||||
|
val statusTag =
|
||||||
|
it.tags().first { it2 ->
|
||||||
|
it2.size > 1 && (it2[0] == "status")
|
||||||
|
}
|
||||||
|
status = statusTag[1]
|
||||||
|
|
||||||
|
if (statusTag.size > 2 && content == "") {
|
||||||
|
// Some DVMs *might* send a the content in the second status tag (even though the NIP says otherwise)
|
||||||
|
content = statusTag[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == "payment-required") {
|
||||||
|
val amountTag =
|
||||||
|
it.tags().first { it2 ->
|
||||||
|
it2.size > 1 && (it2[0] == "amount")
|
||||||
|
}
|
||||||
|
amount = amountTag[1].toLong()
|
||||||
|
|
||||||
|
if (amountTag.size > 2) {
|
||||||
|
// DVM *might* send a lninvoice in the second tag
|
||||||
|
lninvoice = amountTag[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -263,7 +331,10 @@ fun FeedEmptywithStatus(
|
|||||||
model = it,
|
model = it,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier.size(Size75dp).clip(QuoteBorder),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(Size75dp)
|
||||||
|
.clip(QuoteBorder),
|
||||||
)
|
)
|
||||||
} ?: run { NoteAuthorPicture(appDefinitionNote, nav, accountViewModel, Size75dp) }
|
} ?: run { NoteAuthorPicture(appDefinitionNote, nav, accountViewModel, Size75dp) }
|
||||||
|
|
||||||
@@ -277,8 +348,203 @@ fun FeedEmptywithStatus(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = DoubleVertSpacer)
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
Text(content, textAlign = TextAlign.Center)
|
||||||
|
|
||||||
Text(status)
|
if (status == "payment-required") {
|
||||||
|
if (lninvoice != "") {
|
||||||
|
val context = LocalContext.current
|
||||||
|
// TODO is there a better function?
|
||||||
|
Button(onClick = {
|
||||||
|
payViaIntent(
|
||||||
|
lninvoice,
|
||||||
|
context,
|
||||||
|
onPaid = { println("paid") },
|
||||||
|
onError = { println("error") },
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Text(text = "Pay " + (amount / 1000).toString() + " Sats to the DVM")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
statusNote?.let {
|
||||||
|
ZapDVMButton(
|
||||||
|
baseNote = it,
|
||||||
|
amount = amount,
|
||||||
|
grayTint = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ZapDVMButton(
|
||||||
|
baseNote: Note,
|
||||||
|
amount: Long,
|
||||||
|
grayTint: Color,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
iconSize: Dp = Size35dp,
|
||||||
|
iconSizeModifier: Modifier = Size20Modifier,
|
||||||
|
animationSize: Dp = 14.dp,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
var wantsToZap by remember { mutableStateOf<List<Long>?>(null) }
|
||||||
|
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||||
|
var wantsToPay by
|
||||||
|
remember(baseNote) {
|
||||||
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||||
|
persistentListOf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
baseNote.author?.let {
|
||||||
|
LoadUser(baseUserHex = it.pubkeyHex, accountViewModel = accountViewModel) { author ->
|
||||||
|
if (author != null) {
|
||||||
|
author.live().metadata.observeAsState()
|
||||||
|
println(author.info?.lnAddress())
|
||||||
|
println(author.info?.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
||||||
|
var hasZapped by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
customZapClick(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
context,
|
||||||
|
onZappingProgress = { progress: Float ->
|
||||||
|
scope.launch { zappingProgress = progress }
|
||||||
|
},
|
||||||
|
onMultipleChoices = { options -> wantsToZap = options },
|
||||||
|
onError = { _, message ->
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
showErrorMessageDialog = message
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPayViaIntent = { wantsToPay = it },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
if (wantsToZap != null) {
|
||||||
|
ZapAmountChoicePopup(
|
||||||
|
baseNote = baseNote,
|
||||||
|
zapAmountChoices = listOf(amount / 1000),
|
||||||
|
popupYOffset = iconSize,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
onDismiss = {
|
||||||
|
wantsToZap = null
|
||||||
|
zappingProgress = 0f
|
||||||
|
},
|
||||||
|
onChangeAmount = {
|
||||||
|
wantsToZap = null
|
||||||
|
},
|
||||||
|
onError = { _, message ->
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
showErrorMessageDialog = message
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onProgress = {
|
||||||
|
scope.launch(Dispatchers.Main) { zappingProgress = it }
|
||||||
|
},
|
||||||
|
onPayViaIntent = { wantsToPay = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showErrorMessageDialog != null) {
|
||||||
|
ErrorMessageDialog(
|
||||||
|
title = stringResource(id = R.string.error_dialog_zap_error),
|
||||||
|
textContent = showErrorMessageDialog ?: "",
|
||||||
|
onClickStartMessage = {
|
||||||
|
baseNote.author?.let {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val route = routeToMessage(it, showErrorMessageDialog, accountViewModel)
|
||||||
|
nav(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismiss = { showErrorMessageDialog = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantsToPay.isNotEmpty()) {
|
||||||
|
PayViaIntentDialog(
|
||||||
|
payingInvoices = wantsToPay,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
onClose = { wantsToPay = persistentListOf() },
|
||||||
|
onError = {
|
||||||
|
wantsToPay = persistentListOf()
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
showErrorMessageDialog = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
justShowError = {
|
||||||
|
scope.launch {
|
||||||
|
showErrorMessageDialog = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = iconSizeModifier,
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ObserveZapIcon(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
) { wasZappedByLoggedInUser ->
|
||||||
|
LaunchedEffect(wasZappedByLoggedInUser.value) {
|
||||||
|
hasZapped = wasZappedByLoggedInUser.value
|
||||||
|
if (wasZappedByLoggedInUser.value && !accountViewModel.account.hasDonatedInThisVersion()) {
|
||||||
|
delay(1000)
|
||||||
|
accountViewModel.markDonatedInThisVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Crossfade(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon") {
|
||||||
|
if (it) {
|
||||||
|
ZappedIcon(iconSizeModifier)
|
||||||
|
} else {
|
||||||
|
ZapIcon(iconSizeModifier, grayTint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasZapped) {
|
||||||
|
Text(text = stringResource(id = R.string.thank_you))
|
||||||
|
} else {
|
||||||
|
Text(text = "Zap " + (amount / 1000).toString() + " Sats to the DVM") // stringResource(id = R.string.donate_now))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user