Merge branch 'main' into non-english-hashtags

This commit is contained in:
Vitor Pamplona
2023-04-27 15:28:08 -04:00
committed by GitHub
10 changed files with 240 additions and 64 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst" applicationId "com.vitorpamplona.amethyst"
minSdk 26 minSdk 26
targetSdk 33 targetSdk 33
versionCode 127 versionCode 128
versionName "0.37.0" versionName "0.37.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@@ -422,7 +422,7 @@ class Account(
return LocalCache.notes[signedEvent.id] return LocalCache.notes[signedEvent.id]
} }
fun sendPost(message: String, replyTo: List<Note>?, mentions: List<User>?, tags: List<String>? = null) { fun sendPost(message: String, replyTo: List<Note>?, mentions: List<User>?, tags: List<String>? = null, zapReceiver: String? = null) {
if (!isWriteable()) return if (!isWriteable()) return
val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex } val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex }
@@ -435,6 +435,7 @@ class Account(
mentions = mentionsHex, mentions = mentionsHex,
addresses = addresses, addresses = addresses,
extraTags = tags, extraTags = tags,
zapReceiver = zapReceiver,
privateKey = loggedIn.privKey!! privateKey = loggedIn.privKey!!
) )
@@ -450,7 +451,8 @@ class Account(
valueMaximum: Int?, valueMaximum: Int?,
valueMinimum: Int?, valueMinimum: Int?,
consensusThreshold: Int?, consensusThreshold: Int?,
closedAt: Int? closedAt: Int?,
zapReceiver: String? = null
) { ) {
if (!isWriteable()) return if (!isWriteable()) return
@@ -468,14 +470,15 @@ class Account(
valueMaximum = valueMaximum, valueMaximum = valueMaximum,
valueMinimum = valueMinimum, valueMinimum = valueMinimum,
consensusThreshold = consensusThreshold, consensusThreshold = consensusThreshold,
closedAt = closedAt closedAt = closedAt,
zapReceiver = zapReceiver
) )
// println("Sending new PollNoteEvent: %s".format(signedEvent.toJson())) // println("Sending new PollNoteEvent: %s".format(signedEvent.toJson()))
Client.send(signedEvent) Client.send(signedEvent)
LocalCache.consume(signedEvent) LocalCache.consume(signedEvent)
} }
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?) { fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null) {
if (!isWriteable()) return if (!isWriteable()) return
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null } // val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
@@ -487,13 +490,14 @@ class Account(
channel = toChannel, channel = toChannel,
replyTos = repliesToHex, replyTos = repliesToHex,
mentions = mentionsHex, mentions = mentionsHex,
zapReceiver = zapReceiver,
privateKey = loggedIn.privKey!! privateKey = loggedIn.privKey!!
) )
Client.send(signedEvent) Client.send(signedEvent)
LocalCache.consume(signedEvent, null) LocalCache.consume(signedEvent, null)
} }
fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null, mentions: List<User>?) { fun sendPrivateMessage(message: String, toUser: String, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null) {
if (!isWriteable()) return if (!isWriteable()) return
val user = LocalCache.users[toUser] ?: return val user = LocalCache.users[toUser] ?: return
@@ -506,6 +510,7 @@ class Account(
msg = message, msg = message,
replyTos = repliesToHex, replyTos = repliesToHex,
mentions = mentionsHex, mentions = mentionsHex,
zapReceiver = zapReceiver,
privateKey = loggedIn.privKey!!, privateKey = loggedIn.privKey!!,
advertiseNip18 = false advertiseNip18 = false
) )
@@ -20,7 +20,7 @@ class ChannelMessageEvent(
companion object { companion object {
const val kind = 42 const val kind = 42
fun create(message: String, channel: String, replyTos: List<String>? = null, mentions: List<String>? = null, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMessageEvent { fun create(message: String, channel: String, replyTos: List<String>? = null, mentions: List<String>? = null, zapReceiver: String?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMessageEvent {
val content = message val content = message
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
val tags = mutableListOf( val tags = mutableListOf(
@@ -32,6 +32,9 @@ class ChannelMessageEvent(
mentions?.forEach { mentions?.forEach {
tags.add(listOf("p", it)) tags.add(listOf("p", it))
} }
zapReceiver?.let {
tags.add(listOf("zap", it))
}
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, content)
val sig = Utils.sign(id, privateKey) val sig = Utils.sign(id, privateKey)
@@ -48,7 +48,8 @@ class PollNoteEvent(
valueMaximum: Int?, valueMaximum: Int?,
valueMinimum: Int?, valueMinimum: Int?,
consensusThreshold: Int?, consensusThreshold: Int?,
closedAt: Int? closedAt: Int?,
zapReceiver: String?
): PollNoteEvent { ): PollNoteEvent {
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
val tags = mutableListOf<List<String>>() val tags = mutableListOf<List<String>>()
@@ -68,6 +69,11 @@ class PollNoteEvent(
tags.add(listOf(VALUE_MINIMUM, valueMinimum.toString())) tags.add(listOf(VALUE_MINIMUM, valueMinimum.toString()))
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) {
tags.add(listOf("zap", zapReceiver))
}
val id = generateId(pubKey, createdAt, kind, tags, msg) val id = generateId(pubKey, createdAt, kind, tags, msg)
val sig = Utils.sign(id, privateKey) val sig = Utils.sign(id, privateKey)
return PollNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey()) return PollNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
@@ -58,6 +58,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?,
privateKey: ByteArray, privateKey: ByteArray,
createdAt: Long = Date().time / 1000, createdAt: Long = Date().time / 1000,
publishedRecipientPubKey: ByteArray? = null, publishedRecipientPubKey: ByteArray? = null,
@@ -79,6 +80,9 @@ class PrivateDmEvent(
mentions?.forEach { mentions?.forEach {
tags.add(listOf("p", it)) tags.add(listOf("p", it))
} }
zapReceiver?.let {
tags.add(listOf("zap", it))
}
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, content)
val sig = Utils.sign(id, privateKey) val sig = Utils.sign(id, privateKey)
return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
@@ -26,6 +26,7 @@ class TextNoteEvent(
mentions: List<String>?, mentions: List<String>?,
addresses: List<ATag>?, addresses: List<ATag>?,
extraTags: List<String>?, extraTags: List<String>?,
zapReceiver: String?,
privateKey: ByteArray, privateKey: ByteArray,
createdAt: Long = Date().time / 1000 createdAt: Long = Date().time / 1000
): TextNoteEvent { ): TextNoteEvent {
@@ -46,9 +47,13 @@ class TextNoteEvent(
extraTags?.forEach { extraTags?.forEach {
tags.add(listOf("t", it)) tags.add(listOf("t", it))
} }
zapReceiver?.let {
tags.add(listOf("zap", it))
}
findURLs(msg).forEach { findURLs(msg).forEach {
tags.add(listOf("r", it)) tags.add(listOf("r", it))
} }
val id = generateId(pubKey, createdAt, kind, tags, msg) val id = generateId(pubKey, createdAt, kind, tags, msg)
val sig = Utils.sign(id, privateKey) val sig = Utils.sign(id, privateKey)
return TextNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey()) return TextNoteEvent(id.toHexKey(), pubKey, createdAt, tags, msg, sig.toHexKey())
@@ -11,7 +11,6 @@ import okio.BufferedSink
import okio.source import okio.source
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.*
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
@@ -35,7 +34,7 @@ object ImageUploader {
val myServer = if (server == ServersAvailable.IMGUR) { val myServer = if (server == ServersAvailable.IMGUR) {
ImgurServer() ImgurServer()
} else if (server == ServersAvailable.NOSTR_IMG) { } else if (server == ServersAvailable.NOSTRIMG) {
NostrImgServer() NostrImgServer()
} else { } else {
ImgurServer() ImgurServer()
@@ -17,8 +17,12 @@ 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.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.Cancel
import androidx.compose.material.icons.filled.CurrencyBitcoin 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.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -205,17 +209,19 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
val url = postViewModel.contentToAddUrl val url = postViewModel.contentToAddUrl
if (url != null) { if (url != null) {
ImageVideoDescription( Row(verticalAlignment = Alignment.CenterVertically) {
url, ImageVideoDescription(
account.defaultFileServer, url,
onAdd = { description, server -> account.defaultFileServer,
postViewModel.upload(url, description, server, context) onAdd = { description, server ->
account.changeDefaultFileServer(server) postViewModel.upload(url, description, server, context)
}, account.changeDefaultFileServer(server)
onCancel = { },
postViewModel.contentToAddUrl = null onCancel = {
} postViewModel.contentToAddUrl = null
) }
)
}
} }
val user = postViewModel.account?.userProfile() 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( UploadFromGallery(
isUploading = postViewModel.isUploadingImage, isUploading = postViewModel.isUploadingImage,
tint = MaterialTheme.colors.onBackground, tint = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(bottom = 10.dp) modifier = Modifier
) { ) {
postViewModel.selectImage(it) postViewModel.selectImage(it)
} }
@@ -323,6 +329,10 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
postViewModel.wantsInvoice = !postViewModel.wantsInvoice 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 @Composable
fun CloseButton(onCancel: () -> Unit) { fun CloseButton(onCancel: () -> Unit) {
Button( Button(
@@ -489,7 +564,12 @@ fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier
} }
enum class ServersAvailable { enum class ServersAvailable {
IMGUR, NOSTR_BUILD, NOSTR_IMG, NIP95 IMGUR,
NOSTR_BUILD,
NOSTRIMG,
IMGUR_NIP_94,
NOSTRIMG_NIP_94,
NIP95
} }
@Composable @Composable
@@ -507,12 +587,15 @@ fun ImageVideoDescription(
val isVideo = mediaType.startsWith("video") val isVideo = mediaType.startsWith("video")
val fileServers = listOf( val fileServers = listOf(
Pair(ServersAvailable.IMGUR, "imgur.com"), Triple(ServersAvailable.IMGUR, "imgur.com", "Uploads to ImgUR. ImgUR can change your image at any time"),
Pair(ServersAvailable.NOSTR_IMG, "nostrimg.com"), Triple(ServersAvailable.NOSTRIMG, "nostrimg.com", "Regular NostrImg. NostrImg can change your image at any time"),
Pair(ServersAvailable.NIP95, "your relays (NIP-95)") 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 fileServerOptions = fileServers.map { it.second }
val fileServerExplainers = fileServers.map { it.third }
var selectedServer by remember { mutableStateOf(defaultServer) } var selectedServer by remember { mutableStateOf(defaultServer) }
var message by remember { mutableStateOf("") } var message by remember { mutableStateOf("") }
@@ -555,7 +638,7 @@ fun ImageVideoDescription(
fontWeight = FontWeight.W500, fontWeight = FontWeight.W500,
modifier = Modifier modifier = Modifier
.padding(start = 10.dp) .padding(start = 10.dp)
.weight(1.0f) .weight(1.0f).windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
) )
IconButton( IconButton(
@@ -579,7 +662,7 @@ fun ImageVideoDescription(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 10.dp) .padding(bottom = 10.dp).windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
) { ) {
if (mediaType.startsWith("image")) { if (mediaType.startsWith("image")) {
AsyncImage( AsyncImage(
@@ -588,7 +671,7 @@ fun ImageVideoDescription(
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier modifier = Modifier
.padding(top = 4.dp) .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) { } else if (mediaType.startsWith("video") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
var bitmap by remember { mutableStateOf<Bitmap?>(null) } var bitmap by remember { mutableStateOf<Bitmap?>(null) }
@@ -622,33 +705,41 @@ fun ImageVideoDescription(
label = stringResource(id = R.string.file_server), label = stringResource(id = R.string.file_server),
placeholder = fileServers.filter { it.first == defaultServer }.firstOrNull()?.second ?: fileServers[0].second, placeholder = fileServers.filter { it.first == defaultServer }.firstOrNull()?.second ?: fileServers[0].second,
options = fileServerOptions, options = fileServerOptions,
explainers = fileServerExplainers,
onSelect = { onSelect = {
selectedServer = fileServers[it].first selectedServer = fileServers[it].first
}, },
modifier = Modifier modifier = Modifier.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
.weight(1f) .weight(1f)
) )
} }
Row( if (selectedServer == ServersAvailable.NOSTRIMG_NIP_94 ||
verticalAlignment = Alignment.CenterVertically, selectedServer == ServersAvailable.IMGUR_NIP_94 ||
modifier = Modifier.fillMaxWidth() selectedServer == ServersAvailable.NIP95
) { ) {
OutlinedTextField( Row(
label = { Text(text = stringResource(R.string.content_description)) }, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth()
value = message, .windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp))
onValueChange = { message = it }, ) {
placeholder = { OutlinedTextField(
Text( label = { Text(text = stringResource(R.string.content_description)) },
text = stringResource(R.string.content_description_example), modifier = Modifier.fillMaxWidth()
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) .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( Button(
@@ -37,6 +37,7 @@ open class NewPostViewModel : ViewModel() {
var userSuggestions by mutableStateOf<List<User>>(emptyList()) var userSuggestions by mutableStateOf<List<User>>(emptyList())
var userSuggestionAnchor: TextRange? = null var userSuggestionAnchor: TextRange? = null
var userSuggestionsMainMessage: Boolean? = null
// Images and Videos // Images and Videos
var contentToAddUrl by mutableStateOf<Uri?>(null) var contentToAddUrl by mutableStateOf<Uri?>(null)
@@ -61,6 +62,11 @@ open class NewPostViewModel : ViewModel() {
var canAddInvoice by mutableStateOf(false) var canAddInvoice by mutableStateOf(false)
var wantsInvoice by mutableStateOf(false) var wantsInvoice by mutableStateOf(false)
// Forward Zap to
var wantsForwardZapTo by mutableStateOf(false)
var forwardZapTo by mutableStateOf<User?>(null)
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
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 ->
@@ -90,6 +96,10 @@ open class NewPostViewModel : ViewModel() {
canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channel() == null canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channel() == null
contentToAddUrl = null contentToAddUrl = null
wantsForwardZapTo = false
forwardZapTo = null
forwardZapToEditting = TextFieldValue("")
this.account = account this.account = account
} }
@@ -97,14 +107,24 @@ open class NewPostViewModel : ViewModel() {
val tagger = NewMessageTagger(originalNote?.channel(), mentions, replyTos, message.text) val tagger = NewMessageTagger(originalNote?.channel(), mentions, replyTos, message.text)
tagger.run() tagger.run()
if (wantsPoll) { val zapReceiver = if (wantsForwardZapTo) {
account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt) if (forwardZapTo != null) {
} else if (originalNote?.channel() != null) { forwardZapTo?.info?.lud16 ?: forwardZapTo?.info?.lud06
account?.sendChannelMessage(tagger.message, tagger.channel!!.idHex, tagger.replyTos, tagger.mentions) } else {
} else if (originalNote?.event is PrivateDmEvent) { forwardZapToEditting.text
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions) }
} else { } 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() cancel()
@@ -127,7 +147,13 @@ open class NewPostViewModel : ViewModel() {
server = server, server = server,
contentResolver = contentResolver, contentResolver = contentResolver,
onSuccess = { imageUrl, mimeType -> 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 = { onError = {
isUploadingImage = false isUploadingImage = false
@@ -155,6 +181,14 @@ open class NewPostViewModel : ViewModel() {
closedAt = null closedAt = null
wantsInvoice = false wantsInvoice = false
wantsForwardZapTo = false
forwardZapTo = null
forwardZapToEditting = TextFieldValue("")
userSuggestions = emptyList()
userSuggestionAnchor = null
userSuggestionsMainMessage = null
} }
open fun findUrlInMessage(): String? { open fun findUrlInMessage(): String? {
@@ -176,6 +210,21 @@ open class NewPostViewModel : ViewModel() {
if (it.selection.collapsed) { if (it.selection.collapsed) {
val lastWord = it.text.substring(0, it.selection.end).substringAfterLast("\n").substringAfterLast(" ") val lastWord = it.text.substring(0, it.selection.end).substringAfterLast("\n").substringAfterLast(" ")
userSuggestionAnchor = it.selection 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) { if (lastWord.startsWith("@") && lastWord.length > 2) {
userSuggestions = LocalCache.findUsersStartingWith(lastWord.removePrefix("@")) userSuggestions = LocalCache.findUsersStartingWith(lastWord.removePrefix("@"))
} else { } else {
@@ -186,15 +235,29 @@ open class NewPostViewModel : ViewModel() {
open fun autocompleteWithUser(item: User) { open fun autocompleteWithUser(item: User) {
userSuggestionAnchor?.let { userSuggestionAnchor?.let {
val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ") if (userSuggestionsMainMessage == true) {
val lastWordStart = it.end - lastWord.length val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
val wordToInsert = "@${item.pubkeyNpub()}" 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 userSuggestionAnchor = null
userSuggestionsMainMessage = null
userSuggestions = emptyList() userSuggestions = emptyList()
} }
} }
+1 -1
View File
@@ -325,5 +325,5 @@
<string name="file_server">File Server</string> <string name="file_server">File Server</string>
<string name="zap_forward_lnAddress">LnAddress or @User</string>
</resources> </resources>