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
|
||||
|
||||
val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex }
|
||||
@@ -369,8 +369,10 @@ class Account(
|
||||
replyTos = repliesToHex,
|
||||
mentions = mentionsHex,
|
||||
addresses = addresses,
|
||||
extraTags = tags,
|
||||
privateKey = loggedIn.privKey!!
|
||||
)
|
||||
|
||||
Client.send(signedEvent)
|
||||
LocalCache.consume(signedEvent)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ open class Note(val idHex: String) {
|
||||
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
|
||||
}
|
||||
|
||||
open fun address() = (event as? LongTextNoteEvent)?.address()
|
||||
open fun address(): ATag? = null
|
||||
|
||||
open fun createdAt() = event?.createdAt()
|
||||
|
||||
@@ -235,6 +235,21 @@ open class Note(val idHex: String) {
|
||||
}.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 {
|
||||
return replies
|
||||
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
|
||||
|
||||
@@ -18,7 +18,15 @@ class TextNoteEvent(
|
||||
companion object {
|
||||
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 tags = mutableListOf<List<String>>()
|
||||
replyTos?.forEach {
|
||||
@@ -33,6 +41,9 @@ class TextNoteEvent(
|
||||
findHashtags(msg).forEach {
|
||||
tags.add(listOf("t", it))
|
||||
}
|
||||
extraTags?.forEach {
|
||||
tags.add(listOf("t", it))
|
||||
}
|
||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||
val sig = Utils.sign(id, privateKey)
|
||||
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()
|
||||
if (baseReward != null) {
|
||||
DisplayReward(baseReward, baseNote, navController)
|
||||
DisplayReward(baseReward, baseNote, account, navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,10 +566,16 @@ fun DisplayUncitedHashtags(
|
||||
fun DisplayReward(
|
||||
baseReward: BigDecimal,
|
||||
baseNote: Note,
|
||||
account: Account,
|
||||
navController: NavController
|
||||
) {
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
Column() {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable { popupExpanded = true }
|
||||
) {
|
||||
ClickableText(
|
||||
text = AnnotatedString("#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 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 {
|
||||
mutableStateOf<BigDecimal?>(
|
||||
baseReward
|
||||
@@ -609,6 +624,12 @@ fun DisplayReward(
|
||||
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()
|
||||
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_pubkey">Wallet Connect Pubkey</string>
|
||||
<string name="wallet_connect_service_relay">Wallet Connect Relay</string>
|
||||
|
||||
<string name="pledge_amount_in_sats">Pledge Amount in Sats</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user