Adds zap splits
This commit is contained in:
@@ -280,7 +280,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createZapRequestFor(note: Note, pollOption: Int?, message: String = "", zapType: LnZapEvent.ZapType): LnZapRequestEvent? {
|
fun createZapRequestFor(note: Note, pollOption: Int?, message: String = "", zapType: LnZapEvent.ZapType, toUser: User?): LnZapRequestEvent? {
|
||||||
if (!isWriteable()) return null
|
if (!isWriteable()) return null
|
||||||
|
|
||||||
note.event?.let { event ->
|
note.event?.let { event ->
|
||||||
@@ -291,7 +291,8 @@ class Account(
|
|||||||
keyPair.privKey!!,
|
keyPair.privKey!!,
|
||||||
pollOption,
|
pollOption,
|
||||||
message,
|
message,
|
||||||
zapType
|
zapType,
|
||||||
|
toUser?.pubkeyHex
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@@ -759,7 +760,7 @@ class Account(
|
|||||||
replyTo: List<Note>?,
|
replyTo: List<Note>?,
|
||||||
mentions: List<User>?,
|
mentions: List<User>?,
|
||||||
tags: List<String>? = null,
|
tags: List<String>? = null,
|
||||||
zapReceiver: String? = null,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
wantsToMarkAsSensitive: Boolean,
|
wantsToMarkAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
replyingTo: String?,
|
replyingTo: String?,
|
||||||
@@ -820,7 +821,7 @@ class Account(
|
|||||||
valueMinimum: Int?,
|
valueMinimum: Int?,
|
||||||
consensusThreshold: Int?,
|
consensusThreshold: Int?,
|
||||||
closedAt: Int?,
|
closedAt: Int?,
|
||||||
zapReceiver: String? = null,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
wantsToMarkAsSensitive: Boolean,
|
wantsToMarkAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
relayList: List<Relay>? = null,
|
relayList: List<Relay>? = null,
|
||||||
@@ -864,7 +865,7 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: List<ZapSplitSetup>? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
@@ -886,7 +887,7 @@ class Account(
|
|||||||
LocalCache.consume(signedEvent, null)
|
LocalCache.consume(signedEvent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendLiveMessage(message: String, toChannel: ATag, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
fun sendLiveMessage(message: String, toChannel: ATag, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: List<ZapSplitSetup>? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
@@ -908,11 +909,11 @@ class Account(
|
|||||||
LocalCache.consume(signedEvent, null)
|
LocalCache.consume(signedEvent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: List<ZapSplitSetup>? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||||
sendPrivateMessage(message, toUser.pubkeyHex, replyingTo, mentions, zapReceiver, wantsToMarkAsSensitive, zapRaiserAmount, geohash)
|
sendPrivateMessage(message, toUser.pubkeyHex, replyingTo, mentions, zapReceiver, wantsToMarkAsSensitive, zapRaiserAmount, geohash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPrivateMessage(message: String, toUser: HexKey, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
fun sendPrivateMessage(message: String, toUser: HexKey, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: List<ZapSplitSetup>? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
@@ -941,7 +942,7 @@ class Account(
|
|||||||
subject: String? = null,
|
subject: String? = null,
|
||||||
replyingTo: Note? = null,
|
replyingTo: Note? = null,
|
||||||
mentions: List<User>?,
|
mentions: List<User>?,
|
||||||
zapReceiver: String? = null,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
wantsToMarkAsSensitive: Boolean,
|
wantsToMarkAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null
|
geohash: String? = null
|
||||||
|
|||||||
+8
-18
@@ -9,11 +9,9 @@ import com.vitorpamplona.quartz.encoders.Lud06
|
|||||||
import com.vitorpamplona.quartz.encoders.toLnUrl
|
import com.vitorpamplona.quartz.encoders.toLnUrl
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.Call
|
|
||||||
import okhttp3.Callback
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
|
||||||
class LightningAddressResolver() {
|
class LightningAddressResolver() {
|
||||||
@@ -62,7 +60,7 @@ class LightningAddressResolver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchLightningInvoice(lnCallback: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
|
suspend fun fetchLightningInvoice(lnCallback: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: suspend (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
|
||||||
val encodedMessage = URLEncoder.encode(message, "utf-8")
|
val encodedMessage = URLEncoder.encode(message, "utf-8")
|
||||||
|
|
||||||
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
|
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
|
||||||
@@ -78,24 +76,15 @@ class LightningAddressResolver() {
|
|||||||
.url(url)
|
.url(url)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
client.newCall(request).execute().use {
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
response.use {
|
|
||||||
if (it.isSuccessful) {
|
if (it.isSuccessful) {
|
||||||
onSuccess(response.body.string())
|
onSuccess(it.body.string())
|
||||||
} else {
|
} else {
|
||||||
onError("Could not fetch invoice from $lnCallback")
|
onError("Could not fetch invoice from $lnCallback")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(call: Call, e: java.io.IOException) {
|
|
||||||
onError("Could not fetch an invoice from $lnCallback. Message ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun lnAddressToLnUrl(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
suspend fun lnAddressToLnUrl(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
||||||
fetchLightningAddressJson(
|
fetchLightningAddressJson(
|
||||||
lnaddress,
|
lnaddress,
|
||||||
@@ -111,7 +100,7 @@ class LightningAddressResolver() {
|
|||||||
milliSats: Long,
|
milliSats: Long,
|
||||||
message: String,
|
message: String,
|
||||||
nostrRequest: String? = null,
|
nostrRequest: String? = null,
|
||||||
onSuccess: (String) -> Unit,
|
onSuccess: suspend (String) -> Unit,
|
||||||
onError: (String) -> Unit,
|
onError: (String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit
|
onProgress: (percent: Float) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -155,13 +144,14 @@ class LightningAddressResolver() {
|
|||||||
|
|
||||||
lnInvoice?.get("pr")?.asText()?.ifBlank { null }?.let { pr ->
|
lnInvoice?.get("pr")?.asText()?.ifBlank { null }?.let { pr ->
|
||||||
// Forces LN Invoice amount to be the requested amount.
|
// Forces LN Invoice amount to be the requested amount.
|
||||||
|
val expectedAmountInSats = BigDecimal(milliSats).divide(BigDecimal(1000), RoundingMode.HALF_UP).toLong()
|
||||||
val invoiceAmount = LnInvoiceUtil.getAmountInSats(pr)
|
val invoiceAmount = LnInvoiceUtil.getAmountInSats(pr)
|
||||||
if (invoiceAmount.multiply(BigDecimal(1000)).toLong() == BigDecimal(milliSats).toLong()) {
|
if (invoiceAmount.toLong() == expectedAmountInSats) {
|
||||||
onProgress(0.7f)
|
onProgress(0.7f)
|
||||||
onSuccess(pr)
|
onSuccess(pr)
|
||||||
} else {
|
} else {
|
||||||
onProgress(0.0f)
|
onProgress(0.0f)
|
||||||
onError("Incorrect invoice amount (${invoiceAmount.toLong()} sats) from server")
|
onError("Incorrect invoice amount (${invoiceAmount.toLong()} sats) from ${lnaddress}. It should have been ${expectedAmountInSats}")
|
||||||
}
|
}
|
||||||
} ?: lnInvoice?.get("reason")?.asText()?.ifBlank { null }?.let { reason ->
|
} ?: lnInvoice?.get("reason")?.asText()?.ifBlank { null }?.let { reason ->
|
||||||
onProgress(0.0f)
|
onProgress(0.0f)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.util.Size
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -82,20 +83,24 @@ import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||||
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
||||||
import com.vitorpamplona.amethyst.ui.components.*
|
import com.vitorpamplona.amethyst.ui.components.*
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.PollIcon
|
import com.vitorpamplona.amethyst.ui.note.PollIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.RegularPostIcon
|
import com.vitorpamplona.amethyst.ui.note.RegularPostIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.MyTextField
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.MyTextField
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
@@ -111,6 +116,7 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.lang.Math.round
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPostView(
|
fun NewPostView(
|
||||||
@@ -321,9 +327,9 @@ fun NewPostView(
|
|||||||
if (postViewModel.wantsForwardZapTo) {
|
if (postViewModel.wantsForwardZapTo) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)
|
modifier = Modifier.padding(top = Size5dp, bottom = Size5dp, start = Size10dp)
|
||||||
) {
|
) {
|
||||||
FowardZapTo(postViewModel)
|
FowardZapTo(postViewModel, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,7 +727,7 @@ fun SendDirectMessageTo(postViewModel: NewPostViewModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FowardZapTo(postViewModel: NewPostViewModel) {
|
fun FowardZapTo(postViewModel: NewPostViewModel, accountViewModel: AccountViewModel) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
@@ -755,7 +761,7 @@ fun FowardZapTo(postViewModel: NewPostViewModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.zap_forward_title),
|
text = stringResource(R.string.zap_split_title),
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontWeight = FontWeight.W500,
|
fontWeight = FontWeight.W500,
|
||||||
modifier = Modifier.padding(start = 10.dp)
|
modifier = Modifier.padding(start = 10.dp)
|
||||||
@@ -765,22 +771,52 @@ fun FowardZapTo(postViewModel: NewPostViewModel) {
|
|||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.zap_forward_explainer),
|
text = stringResource(R.string.zap_split_explainer),
|
||||||
color = MaterialTheme.colors.placeholderText,
|
color = MaterialTheme.colors.placeholderText,
|
||||||
modifier = Modifier.padding(vertical = 10.dp)
|
modifier = Modifier.padding(vertical = 10.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
postViewModel.forwardZapTo.items.forEachIndexed { index, splitItem ->
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size10dp)) {
|
||||||
|
BaseUserPicture(splitItem.key, Size55dp, accountViewModel = accountViewModel)
|
||||||
|
|
||||||
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
UsernameDisplay(splitItem.key, showPlayButton = false)
|
||||||
|
Text(
|
||||||
|
text = String.format("%.0f%%", splitItem.percentage * 100),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
|
||||||
|
Slider(
|
||||||
|
value = splitItem.percentage,
|
||||||
|
onValueChange = { sliderValue ->
|
||||||
|
val rounded = (round(sliderValue * 20)) / 20.0f
|
||||||
|
postViewModel.updateZapPercentage(index, rounded)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = postViewModel.forwardZapToEditting,
|
value = postViewModel.forwardZapToEditting,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
postViewModel.updateZapForwardTo(it)
|
postViewModel.updateZapForwardTo(it)
|
||||||
},
|
},
|
||||||
|
label = { Text(text = stringResource(R.string.zap_split_serarch_and_add_user)) },
|
||||||
label = { Text(text = stringResource(R.string.zap_forward_lnAddress)) },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.zap_forward_lnAddress),
|
text = stringResource(R.string.zap_split_serarch_and_add_user_placeholder),
|
||||||
color = MaterialTheme.colors.placeholderText
|
color = MaterialTheme.colors.placeholderText
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.Split
|
||||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.events.AddressableEvent
|
import com.vitorpamplona.quartz.events.AddressableEvent
|
||||||
@@ -29,6 +30,7 @@ import com.vitorpamplona.quartz.events.ChatMessageEvent
|
|||||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.events.PrivateDmEvent
|
import com.vitorpamplona.quartz.events.PrivateDmEvent
|
||||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||||
|
import com.vitorpamplona.quartz.events.ZapSplitSetup
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@@ -88,9 +90,13 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
var canAddInvoice by mutableStateOf(false)
|
var canAddInvoice by mutableStateOf(false)
|
||||||
var wantsInvoice by mutableStateOf(false)
|
var wantsInvoice by mutableStateOf(false)
|
||||||
|
|
||||||
|
data class ForwardZapSetup(val user: User) {
|
||||||
|
var percentage by mutableStateOf(100)
|
||||||
|
}
|
||||||
|
|
||||||
// Forward Zap to
|
// Forward Zap to
|
||||||
var wantsForwardZapTo by mutableStateOf(false)
|
var wantsForwardZapTo by mutableStateOf(false)
|
||||||
var forwardZapTo by mutableStateOf<User?>(null)
|
var forwardZapTo by mutableStateOf<Split<User>>(Split())
|
||||||
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
|
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
|
||||||
|
|
||||||
// NSFW, Sensitive
|
// NSFW, Sensitive
|
||||||
@@ -151,7 +157,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
wantsToAddGeoHash = false
|
wantsToAddGeoHash = false
|
||||||
wantsZapraiser = false
|
wantsZapraiser = false
|
||||||
zapRaiserAmount = null
|
zapRaiserAmount = null
|
||||||
forwardZapTo = null
|
forwardZapTo = Split()
|
||||||
forwardZapToEditting = TextFieldValue("")
|
forwardZapToEditting = TextFieldValue("")
|
||||||
|
|
||||||
this.account = account
|
this.account = account
|
||||||
@@ -166,10 +172,13 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
val dmUsers = toUsersTagger.mentions
|
val dmUsers = toUsersTagger.mentions
|
||||||
|
|
||||||
val zapReceiver = if (wantsForwardZapTo) {
|
val zapReceiver = if (wantsForwardZapTo) {
|
||||||
if (forwardZapTo != null) {
|
forwardZapTo?.items?.map {
|
||||||
forwardZapTo?.info?.lud16 ?: forwardZapTo?.info?.lud06
|
ZapSplitSetup(
|
||||||
} else {
|
lnAddressOrPubKeyHex = it.key.pubkeyHex,
|
||||||
forwardZapToEditting.text
|
relay = it.key.relaysBeingUsed.keys.firstOrNull(),
|
||||||
|
weight = it.percentage.toDouble(),
|
||||||
|
isLnAddress = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@@ -355,7 +364,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
wantsForwardZapTo = false
|
wantsForwardZapTo = false
|
||||||
wantsToMarkAsSensitive = false
|
wantsToMarkAsSensitive = false
|
||||||
wantsToAddGeoHash = false
|
wantsToAddGeoHash = false
|
||||||
forwardZapTo = null
|
forwardZapTo = Split()
|
||||||
forwardZapToEditting = TextFieldValue("")
|
forwardZapToEditting = TextFieldValue("")
|
||||||
|
|
||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
@@ -460,6 +469,9 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
|
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
|
||||||
)
|
)
|
||||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||||
|
forwardZapTo?.addItem(item)
|
||||||
|
forwardZapToEditting = TextFieldValue("")
|
||||||
|
/*
|
||||||
val lastWord = forwardZapToEditting.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
val lastWord = forwardZapToEditting.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
||||||
val lastWordStart = it.end - lastWord.length
|
val lastWordStart = it.end - lastWord.length
|
||||||
val wordToInsert = "@${item.pubkeyNpub()}"
|
val wordToInsert = "@${item.pubkeyNpub()}"
|
||||||
@@ -468,7 +480,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
forwardZapToEditting = TextFieldValue(
|
forwardZapToEditting = TextFieldValue(
|
||||||
forwardZapToEditting.text.replaceRange(lastWordStart, it.end, wordToInsert),
|
forwardZapToEditting.text.replaceRange(lastWordStart, it.end, wordToInsert),
|
||||||
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
|
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
|
||||||
)
|
)*/
|
||||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.TO_USERS) {
|
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.TO_USERS) {
|
||||||
val lastWord = toUsers.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
val lastWord = toUsers.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
||||||
val lastWordStart = it.end - lastWord.length
|
val lastWordStart = it.end - lastWord.length
|
||||||
@@ -656,6 +668,10 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
isValidvalueMaximum.value = true
|
isValidvalueMaximum.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateZapPercentage(index: Int, sliderValue: Float) {
|
||||||
|
forwardZapTo?.updatePercentage(index, sliderValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class GeohashPrecision(val digits: Int) {
|
enum class GeohashPrecision(val digits: Int) {
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
class SplitItem<T>(val key: T) {
|
||||||
|
var percentage by mutableStateOf(0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Split<T>() {
|
||||||
|
var items: List<SplitItem<T>> by mutableStateOf(emptyList())
|
||||||
|
|
||||||
|
fun addItem(key: T): Int {
|
||||||
|
val wasEqualSplit = isEqualSplit()
|
||||||
|
val newItem = SplitItem(key)
|
||||||
|
items = items.plus(newItem)
|
||||||
|
|
||||||
|
if (wasEqualSplit) {
|
||||||
|
forceEqualSplit()
|
||||||
|
} else {
|
||||||
|
updatePercentage(items.lastIndex, equalSplit())
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.lastIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
fun equalSplit() = 1f / items.size
|
||||||
|
|
||||||
|
fun isEqualSplit(): Boolean {
|
||||||
|
val expectedPercentage = equalSplit()
|
||||||
|
return items.all { (it.percentage - expectedPercentage) < 0.01 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceEqualSplit() {
|
||||||
|
val correctPercentage = equalSplit()
|
||||||
|
items.forEach {
|
||||||
|
it.percentage = correctPercentage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updatePercentage(index: Int, percentage: Float) {
|
||||||
|
if (items.isEmpty()) return
|
||||||
|
|
||||||
|
val splitItem = items.getOrNull(index) ?: return
|
||||||
|
|
||||||
|
if (items.size == 1) {
|
||||||
|
splitItem.percentage = 1f
|
||||||
|
} else {
|
||||||
|
splitItem.percentage = percentage
|
||||||
|
|
||||||
|
println("Update ${items[index].key} to $percentage")
|
||||||
|
|
||||||
|
val othersMustShare = 1.0f - splitItem.percentage
|
||||||
|
|
||||||
|
val othersHave = items.sumOf {
|
||||||
|
if (it == splitItem) 0.0 else it.percentage.toDouble()
|
||||||
|
}.toFloat()
|
||||||
|
|
||||||
|
if (abs(othersHave - othersMustShare) < 0.01) return // nothing to do
|
||||||
|
|
||||||
|
println("Others Must Share $othersMustShare but have $othersHave")
|
||||||
|
|
||||||
|
bottomUpAdjustment(othersMustShare, othersHave, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bottomUpAdjustment(othersMustShare: Float, othersHave: Float, exceptForIndex: Int) {
|
||||||
|
var needToRemove = othersHave - othersMustShare
|
||||||
|
if (needToRemove > 0) {
|
||||||
|
for (i in items.indices.reversed()) {
|
||||||
|
if (i == exceptForIndex) continue // do not update the current item
|
||||||
|
|
||||||
|
if (needToRemove < items[i].percentage) {
|
||||||
|
val oldValue = items[i].percentage
|
||||||
|
items[i].percentage -= needToRemove
|
||||||
|
needToRemove = 0f
|
||||||
|
println("- Updating ${items[i].key} from $oldValue to ${items[i].percentage - needToRemove}. $needToRemove left")
|
||||||
|
} else {
|
||||||
|
val oldValue = items[i].percentage
|
||||||
|
needToRemove -= items[i].percentage
|
||||||
|
items[i].percentage = 0f
|
||||||
|
println("- Updating ${items[i].key} from $oldValue to ${0}. $needToRemove left")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needToRemove < 0.01) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (needToRemove < 0) {
|
||||||
|
if (items.lastIndex == exceptForIndex) {
|
||||||
|
items[items.lastIndex - 1].percentage += -needToRemove
|
||||||
|
} else {
|
||||||
|
items.last().percentage += -needToRemove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,11 +31,15 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.ButtonDefaults
|
import androidx.compose.material.ButtonDefaults
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.LocalTextStyle
|
import androidx.compose.material.LocalTextStyle
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.darkColors
|
import androidx.compose.material.darkColors
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.ArrowForwardIos
|
||||||
|
import androidx.compose.material.icons.outlined.Bolt
|
||||||
import androidx.compose.material.lightColors
|
import androidx.compose.material.lightColors
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -117,11 +121,13 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||||
@@ -173,6 +179,7 @@ import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
|||||||
import com.vitorpamplona.quartz.events.EmojiPackEvent
|
import com.vitorpamplona.quartz.events.EmojiPackEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiUrl
|
import com.vitorpamplona.quartz.events.EmojiUrl
|
||||||
|
import com.vitorpamplona.quartz.events.EventInterface
|
||||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||||
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
|
||||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||||
@@ -1055,6 +1062,60 @@ private fun NoteBody(
|
|||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav
|
nav
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val noteEvent = baseNote.event
|
||||||
|
val zapSplits = remember(noteEvent) { noteEvent?.hasZapSplitSetup() ?: false }
|
||||||
|
if (zapSplits && noteEvent != null) {
|
||||||
|
Spacer(modifier = HalfDoubleVertSpacer)
|
||||||
|
DisplayZapSplits(noteEvent, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun DisplayZapSplits(noteEvent: EventInterface, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val list = remember(noteEvent) { noteEvent.zapSplitSetup() }
|
||||||
|
|
||||||
|
Row(verticalAlignment = CenterVertically) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.height(20.dp)
|
||||||
|
.width(25.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Bolt,
|
||||||
|
contentDescription = stringResource(id = R.string.zaps),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.align(Alignment.CenterStart),
|
||||||
|
tint = BitcoinOrange
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.ArrowForwardIos,
|
||||||
|
contentDescription = stringResource(id = R.string.zaps),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(13.dp)
|
||||||
|
.align(Alignment.CenterEnd),
|
||||||
|
tint = BitcoinOrange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
|
||||||
|
FlowRow {
|
||||||
|
list.forEach {
|
||||||
|
if (it.isLnAddress) {
|
||||||
|
ClickableText(
|
||||||
|
text = AnnotatedString(it.lnAddressOrPubKeyHex),
|
||||||
|
onClick = { },
|
||||||
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
UserPicture(userHex = it.lnAddressOrPubKeyHex, size = 25.dp, accountViewModel = accountViewModel, nav = nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -102,6 +102,32 @@ fun DisplayBlankAuthor(size: Dp, modifier: Modifier = Modifier) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UserPicture(
|
||||||
|
userHex: String,
|
||||||
|
size: Dp,
|
||||||
|
pictureModifier: Modifier = remember { Modifier },
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
LoadUser(baseUserHex = userHex) {
|
||||||
|
if (it != null) {
|
||||||
|
UserPicture(
|
||||||
|
user = it,
|
||||||
|
size = size,
|
||||||
|
pictureModifier = pictureModifier,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DisplayBlankAuthor(
|
||||||
|
size,
|
||||||
|
pictureModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserPicture(
|
fun UserPicture(
|
||||||
user: User,
|
user: User,
|
||||||
|
|||||||
+109
-13
@@ -41,6 +41,7 @@ import com.vitorpamplona.quartz.events.PayInvoiceErrorResponse
|
|||||||
import com.vitorpamplona.quartz.events.ReportEvent
|
import com.vitorpamplona.quartz.events.ReportEvent
|
||||||
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
||||||
import com.vitorpamplona.quartz.events.UserMetadata
|
import com.vitorpamplona.quartz.events.UserMetadata
|
||||||
|
import com.vitorpamplona.quartz.events.ZapSplitSetup
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
@@ -50,6 +51,7 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class AccountViewModel(val account: Account) : ViewModel() {
|
class AccountViewModel(val account: Account) : ViewModel() {
|
||||||
@@ -219,18 +221,120 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun innerZap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
|
private suspend fun innerZap(note: Note, amountMilliSats: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) {
|
||||||
val lud16 = note.event?.zapAddress() ?: note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
val zapSplitSetup = note.event?.zapSplitSetup()
|
||||||
|
|
||||||
|
val zapsToSend = if (!zapSplitSetup.isNullOrEmpty()) {
|
||||||
|
zapSplitSetup
|
||||||
|
} else {
|
||||||
|
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
||||||
|
|
||||||
if (lud16.isNullOrBlank()) {
|
if (lud16.isNullOrBlank()) {
|
||||||
onError(context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats))
|
onError(context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listOf(ZapSplitSetup(lud16, null, weight = 1.0, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalWeight = zapsToSend.sumOf { it.weight }
|
||||||
|
|
||||||
|
val invoicesToPayOnIntent = mutableListOf<String>()
|
||||||
|
|
||||||
|
zapsToSend.forEachIndexed { index, value ->
|
||||||
|
val outerProgressMin = index / zapsToSend.size.toFloat()
|
||||||
|
val outerProgressMax = (index + 1) / zapsToSend.size.toFloat()
|
||||||
|
|
||||||
|
val zapValue =
|
||||||
|
round((amountMilliSats * value.weight / totalWeight) / 1000f).toLong() * 1000
|
||||||
|
|
||||||
|
if (value.isLnAddress) {
|
||||||
|
innerZap(
|
||||||
|
lud16 = value.lnAddressOrPubKeyHex,
|
||||||
|
note = note,
|
||||||
|
amount = zapValue,
|
||||||
|
pollOption = pollOption,
|
||||||
|
message = message,
|
||||||
|
context = context,
|
||||||
|
onError = onError,
|
||||||
|
onProgress = {
|
||||||
|
onProgress((it * (outerProgressMax - outerProgressMin)) + outerProgressMin)
|
||||||
|
},
|
||||||
|
zapType = zapType,
|
||||||
|
onPayInvoiceThroughIntent = {
|
||||||
|
invoicesToPayOnIntent.add(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val user = LocalCache.getUserIfExists(value.lnAddressOrPubKeyHex)
|
||||||
|
val lud16 = user?.info?.lnAddress()
|
||||||
|
|
||||||
|
if (lud16 != null) {
|
||||||
|
innerZap(
|
||||||
|
lud16 = lud16,
|
||||||
|
note = note,
|
||||||
|
amount = zapValue,
|
||||||
|
pollOption = pollOption,
|
||||||
|
message = message,
|
||||||
|
context = context,
|
||||||
|
onError = onError,
|
||||||
|
onProgress = {
|
||||||
|
onProgress((it * (outerProgressMax - outerProgressMin)) + outerProgressMin)
|
||||||
|
},
|
||||||
|
zapType = zapType,
|
||||||
|
overrideUser = user,
|
||||||
|
onPayInvoiceThroughIntent = {
|
||||||
|
invoicesToPayOnIntent.add(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
onError(
|
||||||
|
context.getString(
|
||||||
|
R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats,
|
||||||
|
user?.toBestDisplayName() ?: value.lnAddressOrPubKeyHex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invoicesToPayOnIntent.isNotEmpty()) {
|
||||||
|
payInvoices(bolt11s = invoicesToPayOnIntent, context = context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Awaits for the event to come back to LocalCache.
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
delay(5000)
|
||||||
|
onProgress(1f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun payInvoices(bolt11s: List<String>, context: Context) {
|
||||||
|
val uri = "lightning:" + bolt11s.joinToString("&")
|
||||||
|
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
|
||||||
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
|
||||||
|
ContextCompat.startActivity(context, intent, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun innerZap(
|
||||||
|
lud16: String,
|
||||||
|
note: Note,
|
||||||
|
amount: Long,
|
||||||
|
pollOption: Int?,
|
||||||
|
message: String,
|
||||||
|
context: Context,
|
||||||
|
onError: (String) -> Unit,
|
||||||
|
onProgress: (percent: Float) -> Unit,
|
||||||
|
onPayInvoiceThroughIntent: (String) -> Unit,
|
||||||
|
zapType: LnZapEvent.ZapType,
|
||||||
|
overrideUser: User? = null
|
||||||
|
) {
|
||||||
var zapRequestJson = ""
|
var zapRequestJson = ""
|
||||||
|
|
||||||
if (zapType != LnZapEvent.ZapType.NONZAP) {
|
if (zapType != LnZapEvent.ZapType.NONZAP) {
|
||||||
val zapRequest = account.createZapRequestFor(note, pollOption, message, zapType)
|
val zapRequest = account.createZapRequestFor(note, pollOption, message, zapType, overrideUser)
|
||||||
if (zapRequest != null) {
|
if (zapRequest != null) {
|
||||||
zapRequestJson = zapRequest.toJson()
|
zapRequestJson = zapRequest.toJson()
|
||||||
}
|
}
|
||||||
@@ -263,19 +367,11 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
onProgress(0.8f)
|
onProgress(0.8f)
|
||||||
|
|
||||||
// Awaits for the event to come back to LocalCache.
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
delay(5000)
|
|
||||||
onProgress(0f)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$it"))
|
onPayInvoiceThroughIntent(it)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
ContextCompat.startActivity(context, intent, null)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
onError(context.getString(R.string.lightning_wallets_not_found))
|
onError(context.getString(R.string.lightning_wallets_not_found2))
|
||||||
}
|
}
|
||||||
onProgress(0f)
|
onProgress(0f)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -570,4 +570,16 @@
|
|||||||
<string name="active_for_chats">Chats</string>
|
<string name="active_for_chats">Chats</string>
|
||||||
<string name="active_for_global">Global</string>
|
<string name="active_for_global">Global</string>
|
||||||
<string name="active_for_search">Search</string>
|
<string name="active_for_search">Search</string>
|
||||||
|
|
||||||
|
<string name="zap_split_title">Split and Forward Zaps</string>
|
||||||
|
<string name="zap_split_explainer">Supporting clients will split and forward zaps to the users added here instead of yours</string>
|
||||||
|
<string name="zap_split_serarch_and_add_user">Search and Add User</string>
|
||||||
|
<string name="zap_split_serarch_and_add_user_placeholder">"@User"</string>
|
||||||
|
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">User %1$s does not have a lightning address set up to receive sats</string>
|
||||||
|
<string name="zap_split_weight">Percentage</string>
|
||||||
|
<string name="zap_split_weight_placeholder">25</string>
|
||||||
|
<string name="splitting_zaps_with">Splitting zaps with</string>
|
||||||
|
<string name="forwarding_zaps_to">Forwarding zaps to</string>
|
||||||
|
|
||||||
|
<string name="lightning_wallets_not_found2">Lightning wallets not found</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.vitorpamplona.amethyst
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.Split
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class SplitterTest {
|
||||||
|
@Test
|
||||||
|
fun testSplit() {
|
||||||
|
val mySplit = Split<String>()
|
||||||
|
|
||||||
|
val vitor = mySplit.addItem("Vitor")
|
||||||
|
|
||||||
|
assertEquals(1f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertTrue(mySplit.isEqualSplit())
|
||||||
|
|
||||||
|
val pablo = mySplit.addItem("Pablo")
|
||||||
|
|
||||||
|
assertEquals(0.5f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.5f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertTrue(mySplit.isEqualSplit())
|
||||||
|
|
||||||
|
val gigi = mySplit.addItem("Gigi")
|
||||||
|
|
||||||
|
assertEquals(0.33f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.33f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.33f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
assertTrue(mySplit.isEqualSplit())
|
||||||
|
|
||||||
|
mySplit.updatePercentage(vitor, 0.5f)
|
||||||
|
|
||||||
|
assertEquals(0.5f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.33f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.16f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
assertFalse(mySplit.isEqualSplit())
|
||||||
|
|
||||||
|
mySplit.updatePercentage(vitor, 0.95f)
|
||||||
|
|
||||||
|
assertEquals(0.95f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.05f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.0f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
assertFalse(mySplit.isEqualSplit())
|
||||||
|
|
||||||
|
mySplit.updatePercentage(vitor, 0.15f)
|
||||||
|
|
||||||
|
assertEquals(0.15f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.05f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.80f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
assertFalse(mySplit.isEqualSplit())
|
||||||
|
|
||||||
|
mySplit.updatePercentage(pablo, 0.95f)
|
||||||
|
|
||||||
|
assertEquals(0.05f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.95f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.00f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
|
||||||
|
mySplit.updatePercentage(gigi, 1f)
|
||||||
|
|
||||||
|
assertEquals(0.00f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.00f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(1.00f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
|
||||||
|
mySplit.updatePercentage(vitor, 0.5f)
|
||||||
|
|
||||||
|
assertEquals(0.50f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.00f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.50f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
|
||||||
|
mySplit.updatePercentage(pablo, 0.3f)
|
||||||
|
|
||||||
|
assertEquals(0.50f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.30f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.20f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
|
||||||
|
mySplit.updatePercentage(gigi, 1f)
|
||||||
|
|
||||||
|
assertEquals(0.00f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.00f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(1.00f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
|
||||||
|
mySplit.updatePercentage(gigi, 0.5f)
|
||||||
|
|
||||||
|
assertEquals(0.00f, mySplit.items[vitor].percentage, 0.01f)
|
||||||
|
assertEquals(0.50f, mySplit.items[pablo].percentage, 0.01f)
|
||||||
|
assertEquals(0.50f, mySplit.items[gigi].percentage, 0.01f)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ class ChannelMessageEvent(
|
|||||||
channel: String,
|
channel: String,
|
||||||
replyTos: List<String>? = null,
|
replyTos: List<String>? = null,
|
||||||
mentions: List<String>? = null,
|
mentions: List<String>? = null,
|
||||||
zapReceiver: String?,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
@@ -50,8 +50,8 @@ class ChannelMessageEvent(
|
|||||||
mentions?.forEach {
|
mentions?.forEach {
|
||||||
tags.add(listOf("p", it))
|
tags.add(listOf("p", it))
|
||||||
}
|
}
|
||||||
zapReceiver?.let {
|
zapReceiver?.forEach {
|
||||||
tags.add(listOf("zap", it))
|
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||||
}
|
}
|
||||||
if (markAsSensitive) {
|
if (markAsSensitive) {
|
||||||
tags.add(listOf("content-warning", ""))
|
tags.add(listOf("content-warning", ""))
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class ChatMessageEvent(
|
|||||||
subject: String? = null,
|
subject: String? = null,
|
||||||
replyTos: List<String>? = null,
|
replyTos: List<String>? = null,
|
||||||
mentions: List<String>? = null,
|
mentions: List<String>? = null,
|
||||||
zapReceiver: String? = null,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
markAsSensitive: Boolean = false,
|
markAsSensitive: Boolean = false,
|
||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
@@ -75,8 +75,8 @@ class ChatMessageEvent(
|
|||||||
mentions?.forEach {
|
mentions?.forEach {
|
||||||
tags.add(listOf("p", it, "", "mention"))
|
tags.add(listOf("p", it, "", "mention"))
|
||||||
}
|
}
|
||||||
zapReceiver?.let {
|
zapReceiver?.forEach {
|
||||||
tags.add(listOf("zap", it))
|
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||||
}
|
}
|
||||||
if (markAsSensitive) {
|
if (markAsSensitive) {
|
||||||
tags.add(listOf("content-warning", ""))
|
tags.add(listOf("content-warning", ""))
|
||||||
|
|||||||
@@ -93,7 +93,21 @@ open class Event(
|
|||||||
(it.size > 1 && it[0] == "zapraiser")
|
(it.size > 1 && it[0] == "zapraiser")
|
||||||
}?.get(1)?.toLongOrNull()
|
}?.get(1)?.toLongOrNull()
|
||||||
|
|
||||||
override fun zapAddress() = tags.firstOrNull { it.size > 1 && it[0] == "zap" }?.get(1)
|
override fun hasZapSplitSetup() = tags.any { it.size > 1 && it[0] == "zap" }
|
||||||
|
|
||||||
|
override fun zapSplitSetup(): List<ZapSplitSetup> {
|
||||||
|
return tags.filter { it.size > 1 && it[0] == "zap" }.map {
|
||||||
|
val isLnAddress = it[0].contains("@") || it[0].startsWith("LNURL", true)
|
||||||
|
val weight = if (isLnAddress) 1.0 else (it.getOrNull(3)?.toDoubleOrNull() ?: 0.0)
|
||||||
|
|
||||||
|
ZapSplitSetup(
|
||||||
|
it[1],
|
||||||
|
it.getOrNull(2),
|
||||||
|
weight,
|
||||||
|
isLnAddress
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun taggedAddresses() = tags.filter { it.size > 1 && it[0] == "a" }.mapNotNull {
|
override fun taggedAddresses() = tags.filter { it.size > 1 && it[0] == "a" }.mapNotNull {
|
||||||
val aTagValue = it[1]
|
val aTagValue = it[1]
|
||||||
@@ -421,3 +435,9 @@ fun String.bytesUsedInMemory(): Int {
|
|||||||
return (8 * ((((this.length) * 2) + 45) / 8))
|
return (8 * ((((this.length) * 2) + 45) / 8))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ZapSplitSetup(
|
||||||
|
val lnAddressOrPubKeyHex: String,
|
||||||
|
val relay: String?,
|
||||||
|
val weight: Double,
|
||||||
|
val isLnAddress: Boolean,
|
||||||
|
)
|
||||||
@@ -58,7 +58,7 @@ interface EventInterface {
|
|||||||
fun getPoWRank(): Int
|
fun getPoWRank(): Int
|
||||||
fun getGeoHash(): String?
|
fun getGeoHash(): String?
|
||||||
|
|
||||||
fun zapAddress(): String?
|
fun zapSplitSetup(): List<ZapSplitSetup>
|
||||||
fun isSensitive(): Boolean
|
fun isSensitive(): Boolean
|
||||||
fun subject(): String?
|
fun subject(): String?
|
||||||
fun zapraiserAmount(): Long?
|
fun zapraiserAmount(): Long?
|
||||||
@@ -78,4 +78,5 @@ interface EventInterface {
|
|||||||
fun taggedEmojis(): List<EmojiUrl>
|
fun taggedEmojis(): List<EmojiUrl>
|
||||||
fun matchTag1With(text: String): Boolean
|
fun matchTag1With(text: String): Boolean
|
||||||
fun isExpired(): Boolean
|
fun isExpired(): Boolean
|
||||||
|
fun hasZapSplitSetup(): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -48,7 +48,7 @@ class LiveActivitiesChatMessageEvent(
|
|||||||
activity: ATag,
|
activity: ATag,
|
||||||
replyTos: List<String>? = null,
|
replyTos: List<String>? = null,
|
||||||
mentions: List<String>? = null,
|
mentions: List<String>? = null,
|
||||||
zapReceiver: String?,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
@@ -66,8 +66,8 @@ class LiveActivitiesChatMessageEvent(
|
|||||||
mentions?.forEach {
|
mentions?.forEach {
|
||||||
tags.add(listOf("p", it))
|
tags.add(listOf("p", it))
|
||||||
}
|
}
|
||||||
zapReceiver?.let {
|
zapReceiver?.forEach {
|
||||||
tags.add(listOf("zap", it))
|
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||||
}
|
}
|
||||||
if (markAsSensitive) {
|
if (markAsSensitive) {
|
||||||
tags.add(listOf("content-warning", ""))
|
tags.add(listOf("content-warning", ""))
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class LnZapRequestEvent(
|
|||||||
pollOption: Int?,
|
pollOption: Int?,
|
||||||
message: String,
|
message: String,
|
||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
|
toUserPubHex: String?, // Overrides in case of Zap Splits
|
||||||
createdAt: Long = TimeUtils.now()
|
createdAt: Long = TimeUtils.now()
|
||||||
): LnZapRequestEvent {
|
): LnZapRequestEvent {
|
||||||
var content = message
|
var content = message
|
||||||
@@ -72,7 +73,7 @@ class LnZapRequestEvent(
|
|||||||
var pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
var pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
||||||
var tags = listOf(
|
var tags = listOf(
|
||||||
listOf("e", originalNote.id()),
|
listOf("e", originalNote.id()),
|
||||||
listOf("p", originalNote.pubKey()),
|
listOf("p", toUserPubHex ?: originalNote.pubKey()),
|
||||||
listOf("relays") + relays
|
listOf("relays") + relays
|
||||||
)
|
)
|
||||||
if (originalNote is AddressableEvent) {
|
if (originalNote is AddressableEvent) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class NIP24Factory {
|
|||||||
subject: String? = null,
|
subject: String? = null,
|
||||||
replyTos: List<String>? = null,
|
replyTos: List<String>? = null,
|
||||||
mentions: List<String>? = null,
|
mentions: List<String>? = null,
|
||||||
zapReceiver: String? = null,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
markAsSensitive: Boolean = false,
|
markAsSensitive: Boolean = false,
|
||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null
|
geohash: String? = null
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class PollNoteEvent(
|
|||||||
valueMinimum: Int?,
|
valueMinimum: Int?,
|
||||||
consensusThreshold: Int?,
|
consensusThreshold: Int?,
|
||||||
closedAt: Int?,
|
closedAt: Int?,
|
||||||
zapReceiver: String?,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long?,
|
zapRaiserAmount: Long?,
|
||||||
geohash: String? = null
|
geohash: String? = null
|
||||||
@@ -76,8 +76,8 @@ class PollNoteEvent(
|
|||||||
tags.add(listOf(CONSENSUS_THRESHOLD, consensusThreshold.toString()))
|
tags.add(listOf(CONSENSUS_THRESHOLD, consensusThreshold.toString()))
|
||||||
tags.add(listOf(CLOSED_AT, closedAt.toString()))
|
tags.add(listOf(CLOSED_AT, closedAt.toString()))
|
||||||
|
|
||||||
if (zapReceiver != null) {
|
zapReceiver?.forEach {
|
||||||
tags.add(listOf("zap", zapReceiver))
|
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||||
}
|
}
|
||||||
if (markAsSensitive) {
|
if (markAsSensitive) {
|
||||||
tags.add(listOf("content-warning", ""))
|
tags.add(listOf("content-warning", ""))
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class PrivateDmEvent(
|
|||||||
msg: String,
|
msg: String,
|
||||||
replyTos: List<String>? = null,
|
replyTos: List<String>? = null,
|
||||||
mentions: List<String>? = null,
|
mentions: List<String>? = null,
|
||||||
zapReceiver: String?,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
publishedRecipientPubKey: ByteArray? = null,
|
publishedRecipientPubKey: ByteArray? = null,
|
||||||
@@ -110,8 +110,8 @@ class PrivateDmEvent(
|
|||||||
mentions?.forEach {
|
mentions?.forEach {
|
||||||
tags.add(listOf("p", it))
|
tags.add(listOf("p", it))
|
||||||
}
|
}
|
||||||
zapReceiver?.let {
|
zapReceiver?.forEach {
|
||||||
tags.add(listOf("zap", it))
|
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||||
}
|
}
|
||||||
if (markAsSensitive) {
|
if (markAsSensitive) {
|
||||||
tags.add(listOf("content-warning", ""))
|
tags.add(listOf("content-warning", ""))
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class TextNoteEvent(
|
|||||||
mentions: List<String>?,
|
mentions: List<String>?,
|
||||||
addresses: List<ATag>?,
|
addresses: List<ATag>?,
|
||||||
extraTags: List<String>?,
|
extraTags: List<String>?,
|
||||||
zapReceiver: String?,
|
zapReceiver: List<ZapSplitSetup>? = null,
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long?,
|
zapRaiserAmount: Long?,
|
||||||
replyingTo: String?,
|
replyingTo: String?,
|
||||||
@@ -80,8 +80,8 @@ class TextNoteEvent(
|
|||||||
extraTags?.forEach {
|
extraTags?.forEach {
|
||||||
tags.add(listOf("t", it))
|
tags.add(listOf("t", it))
|
||||||
}
|
}
|
||||||
zapReceiver?.let {
|
zapReceiver?.forEach {
|
||||||
tags.add(listOf("zap", it))
|
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||||
}
|
}
|
||||||
findURLs(msg).forEach {
|
findURLs(msg).forEach {
|
||||||
tags.add(listOf("r", it))
|
tags.add(listOf("r", it))
|
||||||
|
|||||||
Reference in New Issue
Block a user