diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index a7ffbe0c8..3e14391f7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -507,6 +507,7 @@ class Account( tags: List? = null, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, + zapRaiserAmount: Long? = null, replyingTo: String?, root: String?, directMentions: Set @@ -525,6 +526,7 @@ class Account( extraTags = tags, zapReceiver = zapReceiver, markAsSensitive = wantsToMarkAsSensitive, + zapRaiserAmount = zapRaiserAmount, replyingTo = replyingTo, root = root, directMentions = directMentions, @@ -545,7 +547,8 @@ class Account( consensusThreshold: Int?, closedAt: Int?, zapReceiver: String? = null, - wantsToMarkAsSensitive: Boolean + wantsToMarkAsSensitive: Boolean, + zapRaiserAmount: Long? = null ) { if (!isWriteable()) return @@ -565,14 +568,15 @@ class Account( consensusThreshold = consensusThreshold, closedAt = closedAt, zapReceiver = zapReceiver, - markAsSensitive = wantsToMarkAsSensitive + markAsSensitive = wantsToMarkAsSensitive, + zapRaiserAmount = zapRaiserAmount ) // println("Sending new PollNoteEvent: %s".format(signedEvent.toJson())) Client.send(signedEvent) LocalCache.consume(signedEvent) } - fun sendChannelMessage(message: String, toChannel: String, replyTo: List?, mentions: List?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean) { + fun sendChannelMessage(message: String, toChannel: String, replyTo: List?, mentions: List?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) { if (!isWriteable()) return // val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null } @@ -586,13 +590,14 @@ class Account( mentions = mentionsHex, zapReceiver = zapReceiver, markAsSensitive = wantsToMarkAsSensitive, + zapRaiserAmount = zapRaiserAmount, privateKey = loggedIn.privKey!! ) Client.send(signedEvent) LocalCache.consume(signedEvent, null) } - fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean) { + fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) { if (!isWriteable()) return val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null } @@ -606,6 +611,7 @@ class Account( mentions = mentionsHex, zapReceiver = zapReceiver, markAsSensitive = wantsToMarkAsSensitive, + zapRaiserAmount = zapRaiserAmount, privateKey = loggedIn.privKey!!, advertiseNip18 = false ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt index 09cacffc4..cefaad710 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt @@ -35,7 +35,8 @@ class ChannelMessageEvent( zapReceiver: String?, privateKey: ByteArray, createdAt: Long = Date().time / 1000, - markAsSensitive: Boolean + markAsSensitive: Boolean, + zapRaiserAmount: Long? ): ChannelMessageEvent { val content = message val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() @@ -54,6 +55,9 @@ class ChannelMessageEvent( if (markAsSensitive) { tags.add(listOf("content-warning", "")) } + zapRaiserAmount?.let { + tags.add(listOf("zapraiser", "$it")) + } val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt index 22aa0c440..18f51912e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt @@ -54,6 +54,10 @@ open class Event( (it.size > 1 && it[0] == "t" && it[1].equals("nude", true)) } + override fun zapraiserAmount() = tags.firstOrNull() { + (it.size > 1 && it[0].equals("zapraiser", true)) + }?.get(1)?.toLongOrNull() + override fun zapAddress() = tags.firstOrNull { it.size > 1 && it[0] == "zap" }?.get(1) fun taggedAddresses() = tags.filter { it.size > 1 && it[0] == "a" }.mapNotNull { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt index 75e49d3c5..cd339a0e0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt @@ -38,4 +38,5 @@ interface EventInterface { fun zapAddress(): String? fun isSensitive(): Boolean + fun zapraiserAmount(): Long? } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt index cd0dfb67d..b6ccc01f6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt @@ -52,7 +52,8 @@ class PollNoteEvent( consensusThreshold: Int?, closedAt: Int?, zapReceiver: String?, - markAsSensitive: Boolean + markAsSensitive: Boolean, + zapRaiserAmount: Long? ): PollNoteEvent { val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val tags = mutableListOf>() @@ -79,6 +80,9 @@ class PollNoteEvent( if (markAsSensitive) { tags.add(listOf("content-warning", "")) } + zapRaiserAmount?.let { + tags.add(listOf("zapraiser", "$it")) + } val id = generateId(pubKey, createdAt, kind, tags, msg) val sig = Utils.sign(id, privateKey) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt index 63e429d0c..96955ee00 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt @@ -78,7 +78,8 @@ class PrivateDmEvent( createdAt: Long = Date().time / 1000, publishedRecipientPubKey: ByteArray? = null, advertiseNip18: Boolean = true, - markAsSensitive: Boolean + markAsSensitive: Boolean, + zapRaiserAmount: Long? ): PrivateDmEvent { val content = Utils.encrypt( if (advertiseNip18) { nip18Advertisement } else { "" } + msg, @@ -102,6 +103,9 @@ class PrivateDmEvent( if (markAsSensitive) { tags.add(listOf("content-warning", "")) } + zapRaiserAmount?.let { + tags.add(listOf("zapraiser", "$it")) + } val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt index 0e8dc8c84..b3c7fbb28 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt @@ -34,6 +34,7 @@ class TextNoteEvent( extraTags: List?, zapReceiver: String?, markAsSensitive: Boolean, + zapRaiserAmount: Long?, replyingTo: String?, root: String?, directMentions: Set, @@ -89,6 +90,9 @@ class TextNoteEvent( if (markAsSensitive) { tags.add(listOf("content-warning", "")) } + zapRaiserAmount?.let { + tags.add(listOf("zapraiser", "$it")) + } val id = generateId(pubKey, createdAt, kind, tags, msg) val sig = Utils.sign(id, privateKey) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 2848ce81f..ebd1cda35 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -22,6 +22,7 @@ import androidx.compose.material.icons.filled.ArrowForwardIos import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.CurrencyBitcoin +import androidx.compose.material.icons.filled.ShowChart import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material.icons.outlined.ArrowForwardIos @@ -279,6 +280,17 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n } } + if (lud16 != null && postViewModel.wantsZapraiser) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp)) { + ZapRaiserRequest( + stringResource(id = R.string.zapraiser), + onSuccess = { + postViewModel.zapRaiserAmount = it + } + ) + } + } + val myUrlPreview = postViewModel.urlPreview if (myUrlPreview != null) { Row(modifier = Modifier.padding(top = 5.dp)) { @@ -374,6 +386,12 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n } } + if (postViewModel.canAddZapRaiser) { + AddZapraiserButton(postViewModel.wantsZapraiser) { + postViewModel.wantsZapraiser = !postViewModel.wantsZapraiser + } + } + MarkAsSensitive(postViewModel) { postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive } @@ -460,6 +478,56 @@ private fun AddPollButton( } } +@Composable +private fun AddZapraiserButton( + isLnInvoiceActive: Boolean, + onClick: () -> Unit +) { + IconButton( + onClick = { + onClick() + } + ) { + Box( + Modifier + .height(20.dp) + .width(25.dp) + ) { + if (!isLnInvoiceActive) { + Icon( + imageVector = Icons.Default.ShowChart, + null, + modifier = Modifier.size(20.dp).align(Alignment.TopStart), + tint = MaterialTheme.colors.onBackground + ) + Icon( + imageVector = Icons.Default.Bolt, + contentDescription = stringResource(R.string.zaps), + modifier = Modifier + .size(13.dp) + .align(Alignment.BottomEnd), + tint = MaterialTheme.colors.onBackground + ) + } else { + Icon( + imageVector = Icons.Default.ShowChart, + null, + modifier = Modifier.size(20.dp).align(Alignment.TopStart), + tint = BitcoinOrange + ) + Icon( + imageVector = Icons.Default.Bolt, + contentDescription = stringResource(R.string.zaps), + modifier = Modifier + .size(13.dp) + .align(Alignment.BottomEnd), + tint = BitcoinOrange + ) + } + } + } +} + @Composable private fun AddLnInvoiceButton( isLnInvoiceActive: Boolean, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index b141a92dd..b3d9791df 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -74,6 +74,11 @@ open class NewPostViewModel() : ViewModel() { // NSFW, Sensitive var wantsToMarkAsSensitive by mutableStateOf(false) + // ZapRaiser + var canAddZapRaiser by mutableStateOf(false) + var wantsZapraiser by mutableStateOf(false) + var zapRaiserAmount by mutableStateOf(0L) + open fun load(account: Account, replyingTo: Note?, quote: Note?) { originalNote = replyingTo replyingTo?.let { replyNote -> @@ -105,11 +110,13 @@ open class NewPostViewModel() : ViewModel() { } canAddInvoice = account.userProfile().info?.lnAddress() != null + canAddZapRaiser = account.userProfile().info?.lnAddress() != null canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channelHex() == null contentToAddUrl = null wantsForwardZapTo = false wantsToMarkAsSensitive = false + wantsZapraiser = false forwardZapTo = null forwardZapToEditting = TextFieldValue("") @@ -131,11 +138,11 @@ open class NewPostViewModel() : ViewModel() { } if (wantsPoll) { - account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver, wantsToMarkAsSensitive) + account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver, wantsToMarkAsSensitive, zapRaiserAmount) } else if (originalNote?.channelHex() != null) { - account?.sendChannelMessage(tagger.message, tagger.channelHex!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive) + account?.sendChannelMessage(tagger.message, tagger.channelHex!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, zapRaiserAmount) } else if (originalNote?.event is PrivateDmEvent) { - account?.sendPrivateMessage(tagger.message, originalNote!!.author!!, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive) + account?.sendPrivateMessage(tagger.message, originalNote!!.author!!, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, zapRaiserAmount) } else { // adds markers val rootId = @@ -151,6 +158,7 @@ open class NewPostViewModel() : ViewModel() { tags = null, zapReceiver = zapReceiver, wantsToMarkAsSensitive = wantsToMarkAsSensitive, + zapRaiserAmount = zapRaiserAmount, replyingTo = replyId, root = rootId, directMentions = tagger.directMentions @@ -228,6 +236,7 @@ open class NewPostViewModel() : ViewModel() { closedAt = null wantsInvoice = false + wantsZapraiser = false wantsForwardZapTo = false wantsToMarkAsSensitive = false @@ -333,8 +342,8 @@ open class NewPostViewModel() : ViewModel() { } fun canPost(): Boolean { - return message.text.isNotBlank() && !isUploadingImage && !wantsInvoice && - (!wantsPoll || pollOptions.values.all { it.isNotEmpty() }) && contentToAddUrl == null + return message.text.isNotBlank() && !isUploadingImage && !wantsInvoice + (!wantsPoll || pollOptions.values.all { it.isNotEmpty() }) && contentToAddUrl == null } fun includePollHashtagInMessage(include: Boolean, hashtag: String) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt new file mode 100644 index 000000000..4ba3fde76 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt @@ -0,0 +1,102 @@ +package com.vitorpamplona.amethyst.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +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.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.theme.placeholderText + +@Composable +fun ZapRaiserRequest( + titleText: String? = null, + onSuccess: (Long) -> Unit +) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 10.dp) + ) { + Icon( + painter = painterResource(R.drawable.lightning), + null, + modifier = Modifier.size(20.dp), + tint = Color.Unspecified + ) + + Text( + text = titleText ?: stringResource(R.string.zapraiser), + fontSize = 20.sp, + fontWeight = FontWeight.W500, + modifier = Modifier.padding(start = 10.dp) + ) + } + + Divider() + + Text( + text = stringResource(R.string.zapraiser_explainer), + color = MaterialTheme.colors.placeholderText, + modifier = Modifier.padding(vertical = 10.dp) + ) + + var amount by remember { mutableStateOf(10000L) } + + OutlinedTextField( + label = { Text(text = stringResource(R.string.zapraiser_target_amount_in_sats)) }, + modifier = Modifier.fillMaxWidth(), + value = amount.toString(), + onValueChange = { + runCatching { + if (it.isEmpty()) { + amount = 0 + } else { + amount = it.toLong() + } + onSuccess(amount) + } + }, + placeholder = { + Text( + text = "1000", + color = MaterialTheme.colors.placeholderText + ) + }, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number + ), + singleLine = true + ) + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index cf3bdf6b0..3f18b9ad4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -2706,7 +2706,9 @@ private fun VerticalRelayPanelWithFlow( val showMoreRelaysButtonIconButtonModifier = Modifier.size(24.dp) val showMoreRelaysButtonIconModifier = Modifier.size(15.dp) -val showMoreRelaysButtonBoxModifer = Modifier.fillMaxWidth().height(25.dp) +val showMoreRelaysButtonBoxModifer = Modifier + .fillMaxWidth() + .height(25.dp) @Composable private fun ShowMoreRelaysButton(onClick: () -> Unit) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index eb01db7df..0beaf0844 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -7,11 +7,13 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -21,6 +23,7 @@ import androidx.compose.material.ButtonDefaults import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.IconButton +import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.ProgressIndicatorDefaults import androidx.compose.material.Text @@ -69,6 +72,7 @@ import com.vitorpamplona.amethyst.ui.screen.CombinedZap import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.ButtonBorder +import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap @@ -87,6 +91,10 @@ fun ReactionsRow(baseNote: Note, showReactionDetail: Boolean, accountViewModel: mutableStateOf(false) } + val zapraiserAmount = remember { + baseNote.event?.zapraiserAmount() + } + Spacer(modifier = Modifier.height(7.dp)) Row(verticalAlignment = CenterVertically, modifier = Modifier.padding(start = 10.dp)) { @@ -119,6 +127,19 @@ fun ReactionsRow(baseNote: Note, showReactionDetail: Boolean, accountViewModel: } } + if (zapraiserAmount != null) { + Spacer(modifier = Modifier.height(4.dp)) + Row( + verticalAlignment = CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(start = if (showReactionDetail) 75.dp else 0.dp), + horizontalArrangement = Arrangement.Start + ) { + RenderZapRaiser(baseNote, zapraiserAmount, wantsToSeeReactions.value, accountViewModel) + } + } + if (showReactionDetail && wantsToSeeReactions.value) { ReactionDetailGallery(baseNote, nav, accountViewModel) } @@ -126,6 +147,70 @@ fun ReactionsRow(baseNote: Note, showReactionDetail: Boolean, accountViewModel: Spacer(modifier = Modifier.height(7.dp)) } +@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") } + + 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) { + zapraiserProgress = percentage + if (percentage > 0.99) { + zapraiserLeft = "0" + } else { + zapraiserLeft = showAmount((zapraiserAmount * (1 - percentage)).toBigDecimal()) + } + } + } + } + } + + val color = if (zapraiserProgress > 0.99) { + Color.Green.copy(alpha = 0.32f) + } else { + MaterialTheme.colors.mediumImportanceLink + } + + Box( + Modifier.padding(end = 10.dp).fillMaxWidth() + ) { + LinearProgressIndicator( + modifier = Modifier.matchParentSize(), + color = color, + progress = zapraiserProgress + ) + + Row( + verticalAlignment = CenterVertically, + modifier = Modifier.padding(2.dp) + ) { + if (details) { + val totalPercentage = remember(zapraiserProgress) { + "${(zapraiserProgress * 100).roundToInt()}%" + } + + Text( + text = stringResource(id = R.string.sats_to_complete, totalPercentage, zapraiserLeft), + modifier = Modifier.padding(start = 5.dp, end = 5.dp, top = 2.dp, bottom = 2.dp), + color = MaterialTheme.colors.placeholderText, + fontSize = 14.sp + ) + } + } + } +} + @Composable private fun ExpandButton(baseNote: Note, wantsToSeeReactions: MutableState) { val zapsState by baseNote.live().zaps.observeAsState() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index 97cbf668b..2d27981f9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -13,6 +13,7 @@ val Shapes = Shapes( large = RoundedCornerShape(0.dp) ) +val SmallBorder = RoundedCornerShape(7.dp) val QuoteBorder = RoundedCornerShape(15.dp) val ButtonBorder = RoundedCornerShape(20.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index 03fb9c4e4..c3f15ccf1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -54,6 +54,9 @@ private val LightPlaceholderText = LightColorPalette.onSurface.copy(alpha = 0.32 private val DarkSubtleBorder = DarkColorPalette.onSurface.copy(alpha = 0.12f) private val LightSubtleBorder = LightColorPalette.onSurface.copy(alpha = 0.12f) +private val DarkZapraiserBackground = BitcoinOrange.copy(0.52f).compositeOver(DarkColorPalette.background) +private val LightZapraiserBackground = BitcoinOrange.copy(0.52f).compositeOver(LightColorPalette.background) + private val DarkImageVerifier = Nip05.copy(0.52f).compositeOver(DarkColorPalette.background) private val LightImageVerifier = Nip05.copy(0.52f).compositeOver(LightColorPalette.background) @@ -75,6 +78,9 @@ val Colors.secondaryButtonBackground: Color val Colors.lessImportantLink: Color get() = if (isLight) LightLessImportantLink else DarkLessImportantLink +val Colors.zapraiserBackground: Color + get() = if (isLight) LightZapraiserBackground else DarkZapraiserBackground + val Colors.mediumImportanceLink: Color get() = if (isLight) LightMediumImportantLink else DarkMediumImportantLink val Colors.veryImportantLink: Color diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a1a84b33d..6978f4087 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -417,4 +417,10 @@ New Reaction Symbol No reaction types selected. Long Press to change + + Zapraiser + Adds a target amount of sats to raise for this post. Supporting clients may show this as a progress bar to incentivize donations + Target Amount in Sats + + Zapraiser at %1$s. %2$s sats to goal