mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 16:30:49 +00:00
Adds Invoice Creation to New Posts.
This commit is contained in:
parent
b0953310c2
commit
8344274011
@ -387,6 +387,10 @@ class UserMetadata {
|
|||||||
return listOfNotNull(name, username, display_name, displayName, nip05, lud06, lud16)
|
return listOfNotNull(name, username, display_name, displayName, nip05, lud06, lud16)
|
||||||
.any { it.startsWith(prefix, true) }
|
.any { it.startsWith(prefix, true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun lnAddress(): String? {
|
||||||
|
return (lud16?.trim() ?: lud06?.trim())?.ifBlank { null }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserLiveData(val user: User) : LiveData<UserState>(UserState(user)) {
|
class UserLiveData(val user: User) : LiveData<UserState>(UserState(user)) {
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.model.parseDirtyWordForKey
|
||||||
|
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||||
|
|
||||||
|
class NewMessageProcessor(var originalNote: Note?, var mentions: List<User>?, var replyTos: List<Note>?, var message: String) {
|
||||||
|
|
||||||
|
open fun addUserToMentions(user: User) {
|
||||||
|
mentions = if (mentions?.contains(user) == true) mentions else mentions?.plus(user) ?: listOf(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun addNoteToReplyTos(note: Note) {
|
||||||
|
note.author?.let { addUserToMentions(it) }
|
||||||
|
replyTos = if (replyTos?.contains(note) == true) replyTos else replyTos?.plus(note) ?: listOf(note)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun tagIndex(user: User): Int {
|
||||||
|
// Postr Events assembles replies before mentions in the tag order
|
||||||
|
return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun tagIndex(note: Note): Int {
|
||||||
|
// Postr Events assembles replies before mentions in the tag order
|
||||||
|
return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun run() {
|
||||||
|
// adds all references to mentions and reply tos
|
||||||
|
message.split('\n').forEach { paragraph: String ->
|
||||||
|
paragraph.split(' ').forEach { word: String ->
|
||||||
|
val results = parseDirtyWordForKey(word)
|
||||||
|
|
||||||
|
if (results?.key?.type == Nip19.Type.USER) {
|
||||||
|
addUserToMentions(LocalCache.getOrCreateUser(results.key.hex))
|
||||||
|
} else if (results?.key?.type == Nip19.Type.NOTE) {
|
||||||
|
addNoteToReplyTos(LocalCache.getOrCreateNote(results.key.hex))
|
||||||
|
} else if (results?.key?.type == Nip19.Type.EVENT) {
|
||||||
|
addNoteToReplyTos(LocalCache.getOrCreateNote(results.key.hex))
|
||||||
|
} else if (results?.key?.type == Nip19.Type.ADDRESS) {
|
||||||
|
val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex)
|
||||||
|
if (note != null) {
|
||||||
|
addNoteToReplyTos(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags the text in the correct order.
|
||||||
|
message = message.split('\n').map { paragraph: String ->
|
||||||
|
paragraph.split(' ').map { word: String ->
|
||||||
|
val results = parseDirtyWordForKey(word)
|
||||||
|
if (results?.key?.type == Nip19.Type.USER) {
|
||||||
|
val user = LocalCache.getOrCreateUser(results.key.hex)
|
||||||
|
|
||||||
|
"#[${tagIndex(user)}]${results.restOfWord}"
|
||||||
|
} else if (results?.key?.type == Nip19.Type.NOTE) {
|
||||||
|
val note = LocalCache.getOrCreateNote(results.key.hex)
|
||||||
|
|
||||||
|
"#[${tagIndex(note)}]${results.restOfWord}"
|
||||||
|
} else if (results?.key?.type == Nip19.Type.EVENT) {
|
||||||
|
val note = LocalCache.getOrCreateNote(results.key.hex)
|
||||||
|
|
||||||
|
"#[${tagIndex(note)}]${results.restOfWord}"
|
||||||
|
} else if (results?.key?.type == Nip19.Type.ADDRESS) {
|
||||||
|
val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex)
|
||||||
|
if (note != null) {
|
||||||
|
"#[${tagIndex(note)}]${results.restOfWord}"
|
||||||
|
} else {
|
||||||
|
word
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
word
|
||||||
|
}
|
||||||
|
}.joinToString(" ")
|
||||||
|
}.joinToString("\n")
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.MonetizationOn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -29,6 +31,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
@ -182,6 +185,28 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val user = postViewModel.account?.userProfile()
|
||||||
|
val lud16 = user?.info?.lnAddress()
|
||||||
|
|
||||||
|
if (lud16 != null && user != null && postViewModel.wantsInvoice) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp)) {
|
||||||
|
InvoiceRequest(
|
||||||
|
lud16,
|
||||||
|
user.pubkeyHex,
|
||||||
|
account,
|
||||||
|
stringResource(id = R.string.lightning_invoice),
|
||||||
|
stringResource(id = R.string.lightning_create_and_add_invoice),
|
||||||
|
onSuccess = {
|
||||||
|
postViewModel.message = TextFieldValue(postViewModel.message.text + "\n\n" + it)
|
||||||
|
postViewModel.wantsInvoice = false
|
||||||
|
},
|
||||||
|
onClose = {
|
||||||
|
postViewModel.wantsInvoice = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val myUrlPreview = postViewModel.urlPreview
|
val myUrlPreview = postViewModel.urlPreview
|
||||||
if (myUrlPreview != null) {
|
if (myUrlPreview != null) {
|
||||||
Row(modifier = Modifier.padding(top = 5.dp)) {
|
Row(modifier = Modifier.padding(top = 5.dp)) {
|
||||||
@ -249,6 +274,12 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
postViewModel.wantsPoll = !postViewModel.wantsPoll
|
postViewModel.wantsPoll = !postViewModel.wantsPoll
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (postViewModel.canAddLnInvoice()) {
|
||||||
|
AddLnInvoiceButton(postViewModel.wantsInvoice) {
|
||||||
|
postViewModel.wantsInvoice = !postViewModel.wantsInvoice
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,6 +315,35 @@ private fun AddPollButton(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AddLnInvoiceButton(
|
||||||
|
isLnInvoiceActive: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (!isLnInvoiceActive) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.MonetizationOn,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = MaterialTheme.colors.onBackground
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.MonetizationOn,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = Color.Green.copy(alpha = 0.52f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CloseButton(onCancel: () -> Unit) {
|
fun CloseButton(onCancel: () -> Unit) {
|
||||||
Button(
|
Button(
|
||||||
|
@ -53,6 +53,10 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
var isValidConsensusThreshold = mutableStateOf(true)
|
var isValidConsensusThreshold = mutableStateOf(true)
|
||||||
var isValidClosedAt = mutableStateOf(true)
|
var isValidClosedAt = mutableStateOf(true)
|
||||||
|
|
||||||
|
// Invoices
|
||||||
|
var wantsInvoice by mutableStateOf(false)
|
||||||
|
|
||||||
|
|
||||||
open fun load(account: Account, replyingTo: Note?, quote: Note?) {
|
open fun load(account: Account, replyingTo: Note?, quote: Note?) {
|
||||||
originalNote = replyingTo
|
originalNote = replyingTo
|
||||||
replyingTo?.let { replyNote ->
|
replyingTo?.let { replyNote ->
|
||||||
@ -199,6 +203,8 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
valueMinimum = null
|
valueMinimum = null
|
||||||
consensusThreshold = null
|
consensusThreshold = null
|
||||||
closedAt = null
|
closedAt = null
|
||||||
|
|
||||||
|
wantsInvoice = false
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun findUrlInMessage(): String? {
|
open fun findUrlInMessage(): String? {
|
||||||
@ -248,11 +254,15 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun canPost(): Boolean {
|
fun canPost(): Boolean {
|
||||||
return message.text.isNotBlank() && !isUploadingImage &&
|
return message.text.isNotBlank() && !isUploadingImage && !wantsInvoice &&
|
||||||
(!wantsPoll || pollOptions.values.all { it.isNotEmpty() })
|
(!wantsPoll || pollOptions.values.all { it.isNotEmpty() })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canUsePoll(): Boolean {
|
fun canUsePoll(): Boolean {
|
||||||
return originalNote?.event !is PrivateDmEvent && originalNote?.channel() == null
|
return originalNote?.event !is PrivateDmEvent && originalNote?.channel() == null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun canAddLnInvoice(): Boolean {
|
||||||
|
return account?.userProfile()?.info?.lnAddress() != null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -36,14 +34,21 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
|
|||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onClose: () -> Unit) {
|
fun InvoiceRequest(
|
||||||
|
lud16: String,
|
||||||
|
toUserPubKeyHex: String,
|
||||||
|
account: Account,
|
||||||
|
titleText: String? = null,
|
||||||
|
buttonText: String? = null,
|
||||||
|
onSuccess: (String) -> Unit,
|
||||||
|
onClose: () -> Unit
|
||||||
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
@ -73,7 +78,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
|
|||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.lightning_tips),
|
text = titleText ?: stringResource(R.string.lightning_tips),
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontWeight = FontWeight.W500,
|
fontWeight = FontWeight.W500,
|
||||||
modifier = Modifier.padding(start = 10.dp)
|
modifier = Modifier.padding(start = 10.dp)
|
||||||
@ -137,13 +142,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
|
|||||||
amount * 1000,
|
amount * 1000,
|
||||||
message,
|
message,
|
||||||
zapRequest?.toJson(),
|
zapRequest?.toJson(),
|
||||||
onSuccess = {
|
onSuccess = onSuccess,
|
||||||
runCatching {
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$it"))
|
|
||||||
startActivity(context, intent, null)
|
|
||||||
}
|
|
||||||
onClose()
|
|
||||||
},
|
|
||||||
onError = {
|
onError = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
||||||
@ -159,7 +158,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
|
|||||||
backgroundColor = MaterialTheme.colors.primary
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(R.string.send_sats), color = Color.White, fontSize = 20.sp)
|
Text(text = buttonText ?: stringResource(R.string.send_sats), color = Color.White, fontSize = 20.sp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.gestures.scrollBy
|
import androidx.compose.foundation.gestures.scrollBy
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@ -27,6 +29,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
@ -39,6 +42,7 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@ -421,6 +425,7 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode
|
|||||||
|
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.Bottom) {
|
Row(verticalAlignment = Alignment.Bottom) {
|
||||||
user.bestDisplayName()?.let {
|
user.bestDisplayName()?.let {
|
||||||
@ -555,9 +560,22 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode
|
|||||||
|
|
||||||
if (zapExpanded) {
|
if (zapExpanded) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp)) {
|
||||||
InvoiceRequest(lud16, baseUser.pubkeyHex, account) {
|
InvoiceRequest(lud16, baseUser.pubkeyHex, account,
|
||||||
zapExpanded = false
|
onSuccess = {
|
||||||
}
|
// pay directly
|
||||||
|
if (account.hasWalletConnectSetup()) {
|
||||||
|
account.sendZapPaymentRequestFor(it)
|
||||||
|
} else {
|
||||||
|
runCatching {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$it"))
|
||||||
|
ContextCompat.startActivity(context, intent, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClose = {
|
||||||
|
zapExpanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -288,4 +288,6 @@
|
|||||||
|
|
||||||
<string name="custom_zaps_add_a_message">Add a public message</string>
|
<string name="custom_zaps_add_a_message">Add a public message</string>
|
||||||
<string name="custom_zaps_add_a_message_example">Thank you for all your work!</string>
|
<string name="custom_zaps_add_a_message_example">Thank you for all your work!</string>
|
||||||
|
|
||||||
|
<string name="lightning_create_and_add_invoice">Create and Add</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user