Support to pledge additional bounty amounts.
This commit is contained in:
@@ -357,7 +357,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPost(message: String, replyTo: List<Note>?, mentions: List<User>?) {
|
fun sendPost(message: String, replyTo: List<Note>?, mentions: List<User>?, tags: List<String>? = null) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex }
|
val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex }
|
||||||
@@ -369,8 +369,10 @@ class Account(
|
|||||||
replyTos = repliesToHex,
|
replyTos = repliesToHex,
|
||||||
mentions = mentionsHex,
|
mentions = mentionsHex,
|
||||||
addresses = addresses,
|
addresses = addresses,
|
||||||
|
extraTags = tags,
|
||||||
privateKey = loggedIn.privKey!!
|
privateKey = loggedIn.privKey!!
|
||||||
)
|
)
|
||||||
|
|
||||||
Client.send(signedEvent)
|
Client.send(signedEvent)
|
||||||
LocalCache.consume(signedEvent)
|
LocalCache.consume(signedEvent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ open class Note(val idHex: String) {
|
|||||||
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
|
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun address() = (event as? LongTextNoteEvent)?.address()
|
open fun address(): ATag? = null
|
||||||
|
|
||||||
open fun createdAt() = event?.createdAt()
|
open fun createdAt() = event?.createdAt()
|
||||||
|
|
||||||
@@ -235,6 +235,21 @@ open class Note(val idHex: String) {
|
|||||||
}.sumOf { it }
|
}.sumOf { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasPledgeBy(user: User): Boolean {
|
||||||
|
return replies
|
||||||
|
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
|
||||||
|
.any {
|
||||||
|
val pledgeValue = try {
|
||||||
|
BigDecimal(it.event?.content())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
// do nothing if it can't convert to bigdecimal
|
||||||
|
}
|
||||||
|
|
||||||
|
pledgeValue != null && it.author != user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun pledgedAmountByOthers(): BigDecimal {
|
fun pledgedAmountByOthers(): BigDecimal {
|
||||||
return replies
|
return replies
|
||||||
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
|
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
|
||||||
|
|||||||
@@ -18,7 +18,15 @@ class TextNoteEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 1
|
const val kind = 1
|
||||||
|
|
||||||
fun create(msg: String, replyTos: List<String>?, mentions: List<String>?, addresses: List<ATag>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): TextNoteEvent {
|
fun create(
|
||||||
|
msg: String,
|
||||||
|
replyTos: List<String>?,
|
||||||
|
mentions: List<String>?,
|
||||||
|
addresses: List<ATag>?,
|
||||||
|
extraTags: List<String>?,
|
||||||
|
privateKey: ByteArray,
|
||||||
|
createdAt: Long = Date().time / 1000
|
||||||
|
): TextNoteEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
replyTos?.forEach {
|
replyTos?.forEach {
|
||||||
@@ -33,6 +41,9 @@ class TextNoteEvent(
|
|||||||
findHashtags(msg).forEach {
|
findHashtags(msg).forEach {
|
||||||
tags.add(listOf("t", it))
|
tags.add(listOf("t", it))
|
||||||
}
|
}
|
||||||
|
extraTags?.forEach {
|
||||||
|
tags.add(listOf("t", it))
|
||||||
|
}
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return TextNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
|
return TextNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
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.width
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedTextField
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||||
|
|
||||||
|
class AddBountyAmountViewModel : ViewModel() {
|
||||||
|
private var account: Account? = null
|
||||||
|
private var bounty: Note? = null
|
||||||
|
|
||||||
|
var nextAmount by mutableStateOf(TextFieldValue(""))
|
||||||
|
|
||||||
|
fun load(account: Account, bounty: Note?) {
|
||||||
|
this.account = account
|
||||||
|
this.bounty = bounty
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendPost() {
|
||||||
|
val newValue = nextAmount.text.trim().toLongOrNull()
|
||||||
|
|
||||||
|
if (newValue != null) {
|
||||||
|
account?.sendPost(
|
||||||
|
newValue.toString(),
|
||||||
|
listOfNotNull(bounty),
|
||||||
|
listOfNotNull(bounty?.author),
|
||||||
|
listOf("bounty-added-reward")
|
||||||
|
)
|
||||||
|
|
||||||
|
nextAmount = TextFieldValue("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
nextAmount = TextFieldValue("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasChanged(): Boolean {
|
||||||
|
return nextAmount.text.trim().toLongOrNull() != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun AddBountyAmountDialog(bounty: Note, account: Account, onClose: () -> Unit) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val postViewModel: AddBountyAmountViewModel = viewModel()
|
||||||
|
|
||||||
|
LaunchedEffect(account) {
|
||||||
|
postViewModel.load(account, bounty)
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { onClose() },
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnClickOutside = false,
|
||||||
|
usePlatformDefaultWidth = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Surface() {
|
||||||
|
Column(modifier = Modifier.padding(10.dp).width(IntrinsicSize.Min)) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
CloseButton(onCancel = {
|
||||||
|
postViewModel.cancel()
|
||||||
|
onClose()
|
||||||
|
})
|
||||||
|
|
||||||
|
PostButton(
|
||||||
|
onPost = {
|
||||||
|
postViewModel.sendPost()
|
||||||
|
onClose()
|
||||||
|
},
|
||||||
|
isActive = postViewModel.hasChanged()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 5.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = stringResource(R.string.pledge_amount_in_sats)) },
|
||||||
|
value = postViewModel.nextAmount,
|
||||||
|
onValueChange = {
|
||||||
|
postViewModel.nextAmount = it
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
|
capitalization = KeyboardCapitalization.None,
|
||||||
|
keyboardType = KeyboardType.Number
|
||||||
|
),
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "10000, 50000, 5000000",
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -310,7 +310,7 @@ fun NoteCompose(
|
|||||||
|
|
||||||
val baseReward = noteEvent.getReward()
|
val baseReward = noteEvent.getReward()
|
||||||
if (baseReward != null) {
|
if (baseReward != null) {
|
||||||
DisplayReward(baseReward, baseNote, navController)
|
DisplayReward(baseReward, baseNote, account, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -566,10 +566,16 @@ fun DisplayUncitedHashtags(
|
|||||||
fun DisplayReward(
|
fun DisplayReward(
|
||||||
baseReward: BigDecimal,
|
baseReward: BigDecimal,
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
|
account: Account,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.clickable { popupExpanded = true }
|
||||||
|
) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = AnnotatedString("#bounty"),
|
text = AnnotatedString("#bounty"),
|
||||||
onClick = { navController.navigate("Hashtag/bounty") },
|
onClick = { navController.navigate("Hashtag/bounty") },
|
||||||
@@ -580,16 +586,25 @@ fun DisplayReward(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Bolt,
|
|
||||||
contentDescription = stringResource(R.string.zaps),
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = BitcoinOrange
|
|
||||||
)
|
|
||||||
|
|
||||||
val repliesState by baseNote.live().replies.observeAsState()
|
val repliesState by baseNote.live().replies.observeAsState()
|
||||||
val replyNote = repliesState?.note
|
val replyNote = repliesState?.note
|
||||||
|
|
||||||
|
if (replyNote?.hasPledgeBy(account.userProfile()) == true) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Bolt,
|
||||||
|
contentDescription = stringResource(R.string.zaps),
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = BitcoinOrange
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Bolt,
|
||||||
|
contentDescription = stringResource(R.string.zaps),
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var rewardAmount by remember {
|
var rewardAmount by remember {
|
||||||
mutableStateOf<BigDecimal?>(
|
mutableStateOf<BigDecimal?>(
|
||||||
baseReward
|
baseReward
|
||||||
@@ -609,6 +624,12 @@ fun DisplayReward(
|
|||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (popupExpanded) {
|
||||||
|
AddBountyAmountDialog(baseNote, account) {
|
||||||
|
popupExpanded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ fun NoteMaster(
|
|||||||
|
|
||||||
val baseReward = noteEvent.getReward()
|
val baseReward = noteEvent.getReward()
|
||||||
if (baseReward != null) {
|
if (baseReward != null) {
|
||||||
DisplayReward(baseReward, baseNote, navController)
|
DisplayReward(baseReward, baseNote, account, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,4 +260,6 @@
|
|||||||
<string name="wallet_connect_service_explainer">Uses your private key to pay zaps without leaving the app. Anyone with access to your Nostr private key will be able to spend your wallet\'s balance. Only keep funds you are ok to lose and use a private relay if possible. The relay operator can see your payments metadata.</string>
|
<string name="wallet_connect_service_explainer">Uses your private key to pay zaps without leaving the app. Anyone with access to your Nostr private key will be able to spend your wallet\'s balance. Only keep funds you are ok to lose and use a private relay if possible. The relay operator can see your payments metadata.</string>
|
||||||
<string name="wallet_connect_service_pubkey">Wallet Connect Pubkey</string>
|
<string name="wallet_connect_service_pubkey">Wallet Connect Pubkey</string>
|
||||||
<string name="wallet_connect_service_relay">Wallet Connect Relay</string>
|
<string name="wallet_connect_service_relay">Wallet Connect Relay</string>
|
||||||
|
|
||||||
|
<string name="pledge_amount_in_sats">Pledge Amount in Sats</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user