diff --git a/app/build.gradle b/app/build.gradle index 27f9c25bb..846c6f530 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 127 - versionName "0.37.0" + versionCode 128 + versionName "0.37.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index de76b406e..2a1cfc26f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -422,7 +422,7 @@ class Account( return LocalCache.notes[signedEvent.id] } - fun sendPost(message: String, replyTo: List?, mentions: List?, tags: List? = null) { + fun sendPost(message: String, replyTo: List?, mentions: List?, tags: List? = null, zapReceiver: String? = null) { if (!isWriteable()) return val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex } @@ -435,6 +435,7 @@ class Account( mentions = mentionsHex, addresses = addresses, extraTags = tags, + zapReceiver = zapReceiver, privateKey = loggedIn.privKey!! ) @@ -450,7 +451,8 @@ class Account( valueMaximum: Int?, valueMinimum: Int?, consensusThreshold: Int?, - closedAt: Int? + closedAt: Int?, + zapReceiver: String? = null ) { if (!isWriteable()) return @@ -468,14 +470,15 @@ class Account( valueMaximum = valueMaximum, valueMinimum = valueMinimum, consensusThreshold = consensusThreshold, - closedAt = closedAt + closedAt = closedAt, + zapReceiver = zapReceiver ) // println("Sending new PollNoteEvent: %s".format(signedEvent.toJson())) Client.send(signedEvent) LocalCache.consume(signedEvent) } - fun sendChannelMessage(message: String, toChannel: String, replyTo: List?, mentions: List?) { + fun sendChannelMessage(message: String, toChannel: String, replyTo: List?, mentions: List?, zapReceiver: String? = null) { if (!isWriteable()) return // val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null } @@ -487,13 +490,14 @@ class Account( channel = toChannel, replyTos = repliesToHex, mentions = mentionsHex, + zapReceiver = zapReceiver, privateKey = loggedIn.privKey!! ) Client.send(signedEvent) LocalCache.consume(signedEvent, null) } - fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null, mentions: List?) { + fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null, mentions: List?, zapReceiver: String? = null) { if (!isWriteable()) return val user = LocalCache.users[toUser] ?: return @@ -506,6 +510,7 @@ class Account( msg = message, replyTos = repliesToHex, mentions = mentionsHex, + zapReceiver = zapReceiver, privateKey = loggedIn.privKey!!, advertiseNip18 = false ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt index e2db58fbd..5fb98d1e3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt @@ -20,7 +20,7 @@ class ChannelMessageEvent( companion object { const val kind = 42 - fun create(message: String, channel: String, replyTos: List? = null, mentions: List? = null, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMessageEvent { + fun create(message: String, channel: String, replyTos: List? = null, mentions: List? = null, zapReceiver: String?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMessageEvent { val content = message val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val tags = mutableListOf( @@ -32,6 +32,9 @@ class ChannelMessageEvent( mentions?.forEach { tags.add(listOf("p", it)) } + zapReceiver?.let { + tags.add(listOf("zap", it)) + } val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt index c91cd4c7a..0cb71f805 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt @@ -48,7 +48,8 @@ class PollNoteEvent( valueMaximum: Int?, valueMinimum: Int?, consensusThreshold: Int?, - closedAt: Int? + closedAt: Int?, + zapReceiver: String? ): PollNoteEvent { val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val tags = mutableListOf>() @@ -68,6 +69,11 @@ class PollNoteEvent( tags.add(listOf(VALUE_MINIMUM, valueMinimum.toString())) tags.add(listOf(CONSENSUS_THRESHOLD, consensusThreshold.toString())) tags.add(listOf(CLOSED_AT, closedAt.toString())) + + if (zapReceiver != null) { + tags.add(listOf("zap", zapReceiver)) + } + val id = generateId(pubKey, createdAt, kind, tags, msg) val sig = Utils.sign(id, privateKey) return PollNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey()) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt index c820d0ca2..c45c27848 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt @@ -58,6 +58,7 @@ class PrivateDmEvent( msg: String, replyTos: List? = null, mentions: List? = null, + zapReceiver: String?, privateKey: ByteArray, createdAt: Long = Date().time / 1000, publishedRecipientPubKey: ByteArray? = null, @@ -79,6 +80,9 @@ class PrivateDmEvent( mentions?.forEach { tags.add(listOf("p", it)) } + zapReceiver?.let { + tags.add(listOf("zap", it)) + } val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt index f64582ba3..b9bc6ab55 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt @@ -26,6 +26,7 @@ class TextNoteEvent( mentions: List?, addresses: List?, extraTags: List?, + zapReceiver: String?, privateKey: ByteArray, createdAt: Long = Date().time / 1000 ): TextNoteEvent { @@ -46,9 +47,13 @@ class TextNoteEvent( extraTags?.forEach { tags.add(listOf("t", it)) } + zapReceiver?.let { + tags.add(listOf("zap", it)) + } findURLs(msg).forEach { tags.add(listOf("r", 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()) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt index 6238ddf9a..7dd5bb23f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageUploader.kt @@ -11,7 +11,6 @@ import okio.BufferedSink import okio.source import java.io.IOException import java.io.InputStream -import java.util.* val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') @@ -35,7 +34,7 @@ object ImageUploader { val myServer = if (server == ServersAvailable.IMGUR) { ImgurServer() - } else if (server == ServersAvailable.NOSTR_IMG) { + } else if (server == ServersAvailable.NOSTRIMG) { NostrImgServer() } else { ImgurServer() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 2350c0907..ae9fc3177 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -17,8 +17,12 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowForwardIos +import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.CurrencyBitcoin +import androidx.compose.material.icons.outlined.ArrowForwardIos +import androidx.compose.material.icons.outlined.Bolt import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -205,17 +209,19 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n val url = postViewModel.contentToAddUrl if (url != null) { - ImageVideoDescription( - url, - account.defaultFileServer, - onAdd = { description, server -> - postViewModel.upload(url, description, server, context) - account.changeDefaultFileServer(server) - }, - onCancel = { - postViewModel.contentToAddUrl = null - } - ) + Row(verticalAlignment = Alignment.CenterVertically) { + ImageVideoDescription( + url, + account.defaultFileServer, + onAdd = { description, server -> + postViewModel.upload(url, description, server, context) + account.changeDefaultFileServer(server) + }, + onCancel = { + postViewModel.contentToAddUrl = null + } + ) + } } val user = postViewModel.account?.userProfile() @@ -301,11 +307,11 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n } } - Row(modifier = Modifier.fillMaxWidth()) { + Row(modifier = Modifier.fillMaxWidth().height(50.dp), verticalAlignment = Alignment.CenterVertically) { UploadFromGallery( isUploading = postViewModel.isUploadingImage, tint = MaterialTheme.colors.onBackground, - modifier = Modifier.padding(bottom = 10.dp) + modifier = Modifier ) { postViewModel.selectImage(it) } @@ -323,6 +329,10 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n postViewModel.wantsInvoice = !postViewModel.wantsInvoice } } + + ForwardZapTo(postViewModel) { + postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo + } } } } @@ -386,6 +396,71 @@ private fun AddLnInvoiceButton( } } +@Composable +private fun ForwardZapTo( + postViewModel: NewPostViewModel, + onClick: () -> Unit +) { + IconButton( + onClick = { + onClick() + } + ) { + Box(Modifier.height(20.dp).width(25.dp)) { + if (!postViewModel.wantsForwardZapTo) { + Icon( + imageVector = Icons.Default.Bolt, + contentDescription = stringResource(R.string.zaps), + modifier = Modifier.size(20.dp).align(Alignment.CenterStart), + tint = MaterialTheme.colors.onBackground + ) + Icon( + imageVector = Icons.Default.ArrowForwardIos, + contentDescription = stringResource(R.string.zaps), + modifier = Modifier.size(13.dp).align(Alignment.CenterEnd), + tint = MaterialTheme.colors.onBackground + ) + } else { + 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 + ) + } + } + } + + if (postViewModel.wantsForwardZapTo) { + OutlinedTextField( + value = postViewModel.forwardZapToEditting, + onValueChange = { + postViewModel.updateZapForwardTo(it) + }, + modifier = Modifier.fillMaxWidth().windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)).padding(0.dp), + placeholder = { + Text( + text = stringResource(R.string.zap_forward_lnAddress), + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + }, + colors = TextFieldDefaults + .outlinedTextFieldColors( + unfocusedBorderColor = Color.Transparent, + focusedBorderColor = Color.Transparent + ), + visualTransformation = UrlUserTagTransformation(MaterialTheme.colors.primary), + textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content) + ) + } +} + @Composable fun CloseButton(onCancel: () -> Unit) { Button( @@ -489,7 +564,12 @@ fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier } enum class ServersAvailable { - IMGUR, NOSTR_BUILD, NOSTR_IMG, NIP95 + IMGUR, + NOSTR_BUILD, + NOSTRIMG, + IMGUR_NIP_94, + NOSTRIMG_NIP_94, + NIP95 } @Composable @@ -507,12 +587,15 @@ fun ImageVideoDescription( val isVideo = mediaType.startsWith("video") val fileServers = listOf( - Pair(ServersAvailable.IMGUR, "imgur.com"), - Pair(ServersAvailable.NOSTR_IMG, "nostrimg.com"), - Pair(ServersAvailable.NIP95, "your relays (NIP-95)") + Triple(ServersAvailable.IMGUR, "imgur.com", "Uploads to ImgUR. ImgUR can change your image at any time"), + Triple(ServersAvailable.NOSTRIMG, "nostrimg.com", "Regular NostrImg. NostrImg can change your image at any time"), + Triple(ServersAvailable.IMGUR_NIP_94, "Verifiable ImgUr (NIP-94)", "Protects from ImgUr changing your image after you post"), + Triple(ServersAvailable.NOSTRIMG_NIP_94, "Verifiable NostrIMG (NIP-94)", "Protects from NostrIMG changing your image after you post"), + Triple(ServersAvailable.NIP95, "Your relays (NIP-95)", "The image is hosted in the relay itself. Your image will be free from a domain name / third-party control") ) val fileServerOptions = fileServers.map { it.second } + val fileServerExplainers = fileServers.map { it.third } var selectedServer by remember { mutableStateOf(defaultServer) } var message by remember { mutableStateOf("") } @@ -555,7 +638,7 @@ fun ImageVideoDescription( fontWeight = FontWeight.W500, modifier = Modifier .padding(start = 10.dp) - .weight(1.0f) + .weight(1.0f).windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) ) IconButton( @@ -579,7 +662,7 @@ fun ImageVideoDescription( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .padding(bottom = 10.dp) + .padding(bottom = 10.dp).windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) ) { if (mediaType.startsWith("image")) { AsyncImage( @@ -588,7 +671,7 @@ fun ImageVideoDescription( contentScale = ContentScale.FillWidth, modifier = Modifier .padding(top = 4.dp) - .fillMaxWidth() + .fillMaxWidth().windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) ) } else if (mediaType.startsWith("video") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { var bitmap by remember { mutableStateOf(null) } @@ -622,33 +705,41 @@ fun ImageVideoDescription( label = stringResource(id = R.string.file_server), placeholder = fileServers.filter { it.first == defaultServer }.firstOrNull()?.second ?: fileServers[0].second, options = fileServerOptions, + explainers = fileServerExplainers, onSelect = { selectedServer = fileServers[it].first }, - modifier = Modifier + modifier = Modifier.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) .weight(1f) ) } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() + if (selectedServer == ServersAvailable.NOSTRIMG_NIP_94 || + selectedServer == ServersAvailable.IMGUR_NIP_94 || + selectedServer == ServersAvailable.NIP95 ) { - OutlinedTextField( - label = { Text(text = stringResource(R.string.content_description)) }, - modifier = Modifier.fillMaxWidth(), - value = message, - onValueChange = { message = it }, - placeholder = { - Text( - text = stringResource(R.string.content_description_example), - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)) + ) { + OutlinedTextField( + label = { Text(text = stringResource(R.string.content_description)) }, + modifier = Modifier.fillMaxWidth() + .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)), + value = message, + onValueChange = { message = it }, + placeholder = { + Text( + text = stringResource(R.string.content_description_example), + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + }, + keyboardOptions = KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.Sentences ) - }, - keyboardOptions = KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.Sentences ) - ) + } } Button( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index 8494fe4d0..22c7994f4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -37,6 +37,7 @@ open class NewPostViewModel : ViewModel() { var userSuggestions by mutableStateOf>(emptyList()) var userSuggestionAnchor: TextRange? = null + var userSuggestionsMainMessage: Boolean? = null // Images and Videos var contentToAddUrl by mutableStateOf(null) @@ -61,6 +62,11 @@ open class NewPostViewModel : ViewModel() { var canAddInvoice by mutableStateOf(false) var wantsInvoice by mutableStateOf(false) + // Forward Zap to + var wantsForwardZapTo by mutableStateOf(false) + var forwardZapTo by mutableStateOf(null) + var forwardZapToEditting by mutableStateOf(TextFieldValue("")) + open fun load(account: Account, replyingTo: Note?, quote: Note?) { originalNote = replyingTo replyingTo?.let { replyNote -> @@ -90,6 +96,10 @@ open class NewPostViewModel : ViewModel() { canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channel() == null contentToAddUrl = null + wantsForwardZapTo = false + forwardZapTo = null + forwardZapToEditting = TextFieldValue("") + this.account = account } @@ -97,14 +107,24 @@ open class NewPostViewModel : ViewModel() { val tagger = NewMessageTagger(originalNote?.channel(), mentions, replyTos, message.text) tagger.run() - if (wantsPoll) { - account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt) - } else if (originalNote?.channel() != null) { - account?.sendChannelMessage(tagger.message, tagger.channel!!.idHex, tagger.replyTos, tagger.mentions) - } else if (originalNote?.event is PrivateDmEvent) { - account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions) + val zapReceiver = if (wantsForwardZapTo) { + if (forwardZapTo != null) { + forwardZapTo?.info?.lud16 ?: forwardZapTo?.info?.lud06 + } else { + forwardZapToEditting.text + } } else { - account?.sendPost(tagger.message, tagger.replyTos, tagger.mentions) + null + } + + if (wantsPoll) { + account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver) + } else if (originalNote?.channel() != null) { + account?.sendChannelMessage(tagger.message, tagger.channel!!.idHex, tagger.replyTos, tagger.mentions, zapReceiver) + } else if (originalNote?.event is PrivateDmEvent) { + account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions, zapReceiver) + } else { + account?.sendPost(tagger.message, tagger.replyTos, tagger.mentions, null, zapReceiver) } cancel() @@ -127,7 +147,13 @@ open class NewPostViewModel : ViewModel() { server = server, contentResolver = contentResolver, onSuccess = { imageUrl, mimeType -> - createNIP94Record(imageUrl, mimeType, description) + if (server == ServersAvailable.IMGUR_NIP_94 || server == ServersAvailable.NOSTRIMG_NIP_94) { + createNIP94Record(imageUrl, mimeType, description) + } else { + isUploadingImage = false + message = TextFieldValue(message.text + "\n\n" + imageUrl) + urlPreview = findUrlInMessage() + } }, onError = { isUploadingImage = false @@ -155,6 +181,14 @@ open class NewPostViewModel : ViewModel() { closedAt = null wantsInvoice = false + + wantsForwardZapTo = false + forwardZapTo = null + forwardZapToEditting = TextFieldValue("") + + userSuggestions = emptyList() + userSuggestionAnchor = null + userSuggestionsMainMessage = null } open fun findUrlInMessage(): String? { @@ -176,6 +210,21 @@ open class NewPostViewModel : ViewModel() { if (it.selection.collapsed) { val lastWord = it.text.substring(0, it.selection.end).substringAfterLast("\n").substringAfterLast(" ") userSuggestionAnchor = it.selection + userSuggestionsMainMessage = true + if (lastWord.startsWith("@") && lastWord.length > 2) { + userSuggestions = LocalCache.findUsersStartingWith(lastWord.removePrefix("@")) + } else { + userSuggestions = emptyList() + } + } + } + + open fun updateZapForwardTo(it: TextFieldValue) { + forwardZapToEditting = it + if (it.selection.collapsed) { + val lastWord = it.text.substring(0, it.selection.end).substringAfterLast("\n").substringAfterLast(" ") + userSuggestionAnchor = it.selection + userSuggestionsMainMessage = false if (lastWord.startsWith("@") && lastWord.length > 2) { userSuggestions = LocalCache.findUsersStartingWith(lastWord.removePrefix("@")) } else { @@ -186,15 +235,29 @@ open class NewPostViewModel : ViewModel() { open fun autocompleteWithUser(item: User) { userSuggestionAnchor?.let { - val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ") - val lastWordStart = it.end - lastWord.length - val wordToInsert = "@${item.pubkeyNpub()}" + if (userSuggestionsMainMessage == true) { + val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ") + val lastWordStart = it.end - lastWord.length + val wordToInsert = "@${item.pubkeyNpub()}" + + message = TextFieldValue( + message.text.replaceRange(lastWordStart, it.end, wordToInsert), + TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length) + ) + } else { + val lastWord = forwardZapToEditting.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ") + val lastWordStart = it.end - lastWord.length + val wordToInsert = "@${item.pubkeyNpub()}" + forwardZapTo = item + + forwardZapToEditting = TextFieldValue( + forwardZapToEditting.text.replaceRange(lastWordStart, it.end, wordToInsert), + TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length) + ) + } - message = TextFieldValue( - message.text.replaceRange(lastWordStart, it.end, wordToInsert), - TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length) - ) userSuggestionAnchor = null + userSuggestionsMainMessage = null userSuggestions = emptyList() } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5fb553efd..3db79ad44 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -325,5 +325,5 @@ File Server - + LnAddress or @User