kopia lustrzana https://github.com/vitorpamplona/amethyst
- Adds support for GeoHash
- Refactors New Post Buttons to make them more similar to one another.pull/531/head
rodzic
c20277a754
commit
1098c31787
|
@ -191,6 +191,9 @@ dependencies {
|
|||
// immutable collections to avoid recomposition
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
|
||||
|
||||
// GeoHash
|
||||
implementation 'com.github.drfonfon:android-kotlin-geohash:1.0'
|
||||
|
||||
// Video compression lib
|
||||
implementation 'com.github.AbedElazizShe:LightCompressor:1.3.1'
|
||||
// Image compression lib
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
<!-- This notification permission is needed for some phones -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<!-- Adds Geohash to posts if active -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<!-- Old permission to access media -->
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
|
|
|
@ -671,7 +671,8 @@ class Account(
|
|||
replyingTo: String?,
|
||||
root: String?,
|
||||
directMentions: Set<HexKey>,
|
||||
relayList: List<Relay>? = null
|
||||
relayList: List<Relay>? = null,
|
||||
geohash: String? = null
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
|
@ -691,6 +692,7 @@ class Account(
|
|||
replyingTo = replyingTo,
|
||||
root = root,
|
||||
directMentions = directMentions,
|
||||
geohash = geohash,
|
||||
privateKey = keyPair.privKey!!
|
||||
)
|
||||
|
||||
|
@ -710,7 +712,8 @@ class Account(
|
|||
zapReceiver: String? = null,
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
relayList: List<Relay>? = null
|
||||
relayList: List<Relay>? = null,
|
||||
geohash: String? = null
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
|
@ -731,14 +734,15 @@ class Account(
|
|||
closedAt = closedAt,
|
||||
zapReceiver = zapReceiver,
|
||||
markAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
geohash = geohash
|
||||
)
|
||||
// println("Sending new PollNoteEvent: %s".format(signedEvent.toJson()))
|
||||
Client.send(signedEvent, relayList = relayList)
|
||||
LocalCache.consume(signedEvent)
|
||||
}
|
||||
|
||||
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) {
|
||||
fun sendChannelMessage(message: String, toChannel: String, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||
|
@ -753,13 +757,14 @@ class Account(
|
|||
zapReceiver = zapReceiver,
|
||||
markAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
geohash = geohash,
|
||||
privateKey = keyPair.privKey!!
|
||||
)
|
||||
Client.send(signedEvent)
|
||||
LocalCache.consume(signedEvent, null)
|
||||
}
|
||||
|
||||
fun sendLiveMessage(message: String, toChannel: ATag, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) {
|
||||
fun sendLiveMessage(message: String, toChannel: ATag, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||
|
@ -774,13 +779,14 @@ class Account(
|
|||
zapReceiver = zapReceiver,
|
||||
markAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
geohash = geohash,
|
||||
privateKey = keyPair.privKey!!
|
||||
)
|
||||
Client.send(signedEvent)
|
||||
LocalCache.consume(signedEvent, null)
|
||||
}
|
||||
|
||||
fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) {
|
||||
fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null, geohash: String? = null) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||
|
@ -795,6 +801,7 @@ class Account(
|
|||
zapReceiver = zapReceiver,
|
||||
markAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
geohash = geohash,
|
||||
privateKey = keyPair.privKey!!,
|
||||
advertiseNip18 = false
|
||||
)
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package com.vitorpamplona.amethyst.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.os.Bundle
|
||||
import android.os.HandlerThread
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
class LocationUtil(context: Context) {
|
||||
companion object {
|
||||
const val MIN_TIME: Long = 1000L
|
||||
const val MIN_DISTANCE: Float = 0.0f
|
||||
}
|
||||
|
||||
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
private var locationListener: LocationListener? = null
|
||||
|
||||
val locationStateFlow = MutableStateFlow<Location>(Location(LocationManager.NETWORK_PROVIDER))
|
||||
val providerState = mutableStateOf(false)
|
||||
val isStart: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
private val locHandlerThread = HandlerThread("LocationUtil Thread")
|
||||
|
||||
init {
|
||||
locHandlerThread.start()
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun start(minTimeMs: Long = MIN_TIME, minDistanceM: Float = MIN_DISTANCE) {
|
||||
locationListener().let {
|
||||
locationListener = it
|
||||
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, minTimeMs, minDistanceM, it, locHandlerThread.looper)
|
||||
}
|
||||
providerState.value = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||
isStart.value = true
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
locationListener?.let {
|
||||
locationManager.removeUpdates(it)
|
||||
}
|
||||
isStart.value = false
|
||||
}
|
||||
|
||||
private fun locationListener() = object : LocationListener {
|
||||
override fun onLocationChanged(location: Location) {
|
||||
locationStateFlow.value = location
|
||||
}
|
||||
|
||||
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
|
||||
}
|
||||
|
||||
override fun onProviderEnabled(provider: String) {
|
||||
providerState.value = true
|
||||
}
|
||||
|
||||
override fun onProviderDisabled(provider: String) {
|
||||
providerState.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReverseGeoLocationUtil {
|
||||
fun execute(
|
||||
location: Location,
|
||||
context: Context
|
||||
): String? {
|
||||
return try {
|
||||
Geocoder(context).getFromLocation(location.latitude, location.longitude, 1)?.firstOrNull()?.let { address ->
|
||||
listOfNotNull(address.locality ?: address.subAdminArea, address.countryCode).joinToString(", ")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,7 +36,8 @@ class ChannelMessageEvent(
|
|||
privateKey: ByteArray,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
markAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long?
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null
|
||||
): ChannelMessageEvent {
|
||||
val content = message
|
||||
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
||||
|
@ -58,6 +59,9 @@ class ChannelMessageEvent(
|
|||
zapRaiserAmount?.let {
|
||||
tags.add(listOf("zapraiser", "$it"))
|
||||
}
|
||||
geohash?.let {
|
||||
tags.add(listOf("g", it))
|
||||
}
|
||||
|
||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||
val sig = CryptoUtils.sign(id, privateKey)
|
||||
|
|
|
@ -122,6 +122,10 @@ open class Event(
|
|||
return rank
|
||||
}
|
||||
|
||||
override fun getGeoHash(): String? {
|
||||
return tags.firstOrNull { it.size > 1 && it[0] == "g" }?.get(1)?.ifBlank { null }
|
||||
}
|
||||
|
||||
override fun getReward(): BigDecimal? {
|
||||
return try {
|
||||
tags.firstOrNull { it.size > 1 && it[0] == "reward" }?.get(1)?.let { BigDecimal(it) }
|
||||
|
|
|
@ -45,6 +45,7 @@ interface EventInterface {
|
|||
|
||||
fun getReward(): BigDecimal?
|
||||
fun getPoWRank(): Int
|
||||
fun getGeoHash(): String?
|
||||
|
||||
fun zapAddress(): String?
|
||||
fun isSensitive(): Boolean
|
||||
|
|
|
@ -51,7 +51,8 @@ class LiveActivitiesChatMessageEvent(
|
|||
privateKey: ByteArray,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
markAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long?
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null
|
||||
): LiveActivitiesChatMessageEvent {
|
||||
val content = message
|
||||
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
||||
|
@ -73,6 +74,9 @@ class LiveActivitiesChatMessageEvent(
|
|||
zapRaiserAmount?.let {
|
||||
tags.add(listOf("zapraiser", "$it"))
|
||||
}
|
||||
geohash?.let {
|
||||
tags.add(listOf("g", it))
|
||||
}
|
||||
|
||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||
val sig = CryptoUtils.sign(id, privateKey)
|
||||
|
|
|
@ -53,7 +53,8 @@ class PollNoteEvent(
|
|||
closedAt: Int?,
|
||||
zapReceiver: String?,
|
||||
markAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long?
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null
|
||||
): PollNoteEvent {
|
||||
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
||||
val tags = mutableListOf<List<String>>()
|
||||
|
@ -83,6 +84,9 @@ class PollNoteEvent(
|
|||
zapRaiserAmount?.let {
|
||||
tags.add(listOf("zapraiser", "$it"))
|
||||
}
|
||||
geohash?.let {
|
||||
tags.add(listOf("g", it))
|
||||
}
|
||||
|
||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||
val sig = CryptoUtils.sign(id, privateKey)
|
||||
|
|
|
@ -86,7 +86,8 @@ class PrivateDmEvent(
|
|||
publishedRecipientPubKey: ByteArray? = null,
|
||||
advertiseNip18: Boolean = true,
|
||||
markAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long?
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null
|
||||
): PrivateDmEvent {
|
||||
val content = CryptoUtils.encrypt(
|
||||
if (advertiseNip18) { nip18Advertisement } else { "" } + msg,
|
||||
|
@ -113,6 +114,10 @@ class PrivateDmEvent(
|
|||
zapRaiserAmount?.let {
|
||||
tags.add(listOf("zapraiser", "$it"))
|
||||
}
|
||||
geohash?.let {
|
||||
tags.add(listOf("g", it))
|
||||
}
|
||||
|
||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||
val sig = CryptoUtils.sign(id, privateKey)
|
||||
return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||
|
|
|
@ -38,6 +38,7 @@ class TextNoteEvent(
|
|||
replyingTo: String?,
|
||||
root: String?,
|
||||
directMentions: Set<HexKey>,
|
||||
geohash: String? = null,
|
||||
|
||||
privateKey: ByteArray,
|
||||
createdAt: Long = TimeUtils.now()
|
||||
|
@ -93,6 +94,9 @@ class TextNoteEvent(
|
|||
zapRaiserAmount?.let {
|
||||
tags.add(listOf("zapraiser", "$it"))
|
||||
}
|
||||
geohash?.let {
|
||||
tags.add(listOf("g", it))
|
||||
}
|
||||
|
||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||
val sig = CryptoUtils.sign(id, privateKey)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import android.Manifest
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
|
@ -22,6 +23,8 @@ 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.CurrencyBitcoin
|
||||
import androidx.compose.material.icons.filled.LocationOff
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.ShowChart
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
|
@ -32,6 +35,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -66,10 +70,15 @@ import androidx.compose.ui.window.Dialog
|
|||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil.compose.AsyncImage
|
||||
import com.fonfon.kgeohash.toGeoHash
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
||||
import com.vitorpamplona.amethyst.ui.components.*
|
||||
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
||||
|
@ -82,8 +91,9 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
|||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
|
@ -92,6 +102,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
@ -278,28 +289,72 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||
)
|
||||
|
||||
if (postViewModel.wantsPoll) {
|
||||
postViewModel.pollOptions.values.forEachIndexed { index, _ ->
|
||||
NewPollOption(postViewModel, index)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { postViewModel.pollOptions[postViewModel.pollOptions.size] = "" },
|
||||
border = BorderStroke(1.dp, MaterialTheme.colors.placeholderText),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)
|
||||
) {
|
||||
Image(
|
||||
painterResource(id = android.R.drawable.ic_input_add),
|
||||
contentDescription = "Add poll option button",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
postViewModel.pollOptions.values.forEachIndexed { index, _ ->
|
||||
NewPollOption(postViewModel, index)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
postViewModel.pollOptions[postViewModel.pollOptions.size] =
|
||||
""
|
||||
},
|
||||
border = BorderStroke(
|
||||
1.dp,
|
||||
MaterialTheme.colors.placeholderText
|
||||
),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
painterResource(id = android.R.drawable.ic_input_add),
|
||||
contentDescription = "Add poll option button",
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsToMarkAsSensitive) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(vertical = Size5dp, horizontal = Size10dp)
|
||||
) {
|
||||
ContentSensitivityExplainer(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsToAddGeoHash) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.padding(vertical = Size5dp, horizontal = Size10dp)
|
||||
) {
|
||||
LocationAsHash(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsForwardZapTo) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)
|
||||
) {
|
||||
FowardZapTo(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
val url = postViewModel.contentToAddUrl
|
||||
if (url != null) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||
ImageVideoDescription(
|
||||
url,
|
||||
account.defaultFileServer,
|
||||
|
@ -324,26 +379,28 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||
val lud16 = user?.info?.lnAddress()
|
||||
|
||||
if (lud16 != 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
|
||||
}
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lud16 != null && postViewModel.wantsZapraiser) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||
ZapRaiserRequest(
|
||||
stringResource(id = R.string.zapraiser),
|
||||
postViewModel
|
||||
|
@ -353,7 +410,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||
|
||||
val myUrlPreview = postViewModel.urlPreview
|
||||
if (myUrlPreview != null) {
|
||||
Row(modifier = Modifier.padding(top = 5.dp)) {
|
||||
Row(modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||
if (isValidURL(myUrlPreview)) {
|
||||
val removedParamsFromUrl =
|
||||
myUrlPreview.split("?")[0].lowercase()
|
||||
|
@ -456,6 +513,10 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||
postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive
|
||||
}
|
||||
|
||||
AddGeoHash(postViewModel) {
|
||||
postViewModel.wantsToAddGeoHash = !postViewModel.wantsToAddGeoHash
|
||||
}
|
||||
|
||||
ForwardZapTo(postViewModel) {
|
||||
postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo
|
||||
}
|
||||
|
@ -466,6 +527,227 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ContentSensitivityExplainer(postViewModel: NewPostViewModel) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.height(20.dp)
|
||||
.width(25.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.VisibilityOff,
|
||||
contentDescription = stringResource(id = R.string.content_warning),
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.align(Alignment.BottomStart),
|
||||
tint = Color.Red
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Warning,
|
||||
contentDescription = stringResource(id = R.string.content_warning),
|
||||
modifier = Modifier
|
||||
.size(10.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
tint = Color.Yellow
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.add_sensitive_content_label),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.add_sensitive_content_explainer),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(vertical = 10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FowardZapTo(postViewModel: NewPostViewModel) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.zap_forward_title),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.zap_forward_explainer),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(vertical = 10.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = postViewModel.forwardZapToEditting,
|
||||
onValueChange = {
|
||||
postViewModel.updateZapForwardTo(it)
|
||||
},
|
||||
|
||||
label = { Text(text = stringResource(R.string.zap_forward_lnAddress)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.zap_forward_lnAddress),
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
visualTransformation = UrlUserTagTransformation(
|
||||
MaterialTheme.colors.primary
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun LocationAsHash(postViewModel: NewPostViewModel) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val locationPermissionState = rememberPermissionState(
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
)
|
||||
|
||||
if (locationPermissionState.status.isGranted) {
|
||||
var locationDescriptionFlow by remember(postViewModel) {
|
||||
mutableStateOf<Flow<String>?>(null)
|
||||
}
|
||||
|
||||
DisposableEffect(key1 = Unit) {
|
||||
postViewModel.startLocation(context = context)
|
||||
locationDescriptionFlow = postViewModel.location
|
||||
|
||||
onDispose {
|
||||
postViewModel.stopLocation()
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
) {
|
||||
Box(
|
||||
Modifier
|
||||
.height(20.dp)
|
||||
.width(20.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.LocationOn,
|
||||
null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colors.primary
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.geohash_title),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
)
|
||||
|
||||
locationDescriptionFlow?.let { geoLocation ->
|
||||
DisplayLocationObserver(geoLocation)
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.geohash_explainer),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(vertical = 10.dp)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LaunchedEffect(locationPermissionState) {
|
||||
locationPermissionState.launchPermissionRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayLocationObserver(geoLocation: Flow<String>) {
|
||||
val location by geoLocation.collectAsState(initial = null)
|
||||
|
||||
location?.let {
|
||||
DisplayLocationInTitle(geohash = it)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayLocationInTitle(geohash: String) {
|
||||
val context = LocalContext.current
|
||||
val cityName = remember(geohash) {
|
||||
ReverseGeoLocationUtil().execute(geohash.toGeoHash().toLocation(), context)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = cityName ?: geohash,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = Size5dp)
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun Notifying(baseMentions: ImmutableList<User>?, onClick: (User) -> Unit) {
|
||||
|
@ -582,6 +864,31 @@ private fun AddZapraiserButton(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddGeoHash(postViewModel: NewPostViewModel, onClick: () -> Unit) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onClick()
|
||||
}
|
||||
) {
|
||||
if (!postViewModel.wantsToAddGeoHash) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.LocationOff,
|
||||
null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colors.onBackground
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.LocationOn,
|
||||
null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colors.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddLnInvoiceButton(
|
||||
isLnInvoiceActive: Boolean,
|
||||
|
@ -662,33 +969,6 @@ private fun ForwardZapTo(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.placeholderText,
|
||||
fontSize = Font14SP
|
||||
)
|
||||
},
|
||||
colors = TextFieldDefaults
|
||||
.outlinedTextFieldColors(
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent
|
||||
),
|
||||
visualTransformation = UrlUserTagTransformation(MaterialTheme.colors.primary),
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -913,7 +1193,9 @@ fun ImageVideoDescription(
|
|||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.size(30.dp).padding(end = 5.dp),
|
||||
modifier = Modifier
|
||||
.size(30.dp)
|
||||
.padding(end = 5.dp),
|
||||
onClick = onCancel
|
||||
) {
|
||||
CancelIcon()
|
||||
|
|
|
@ -13,8 +13,10 @@ import androidx.compose.ui.text.TextRange
|
|||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.fonfon.kgeohash.toGeoHash
|
||||
import com.vitorpamplona.amethyst.model.*
|
||||
import com.vitorpamplona.amethyst.service.FileHeader
|
||||
import com.vitorpamplona.amethyst.service.LocationUtil
|
||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.model.AddressableEvent
|
||||
import com.vitorpamplona.amethyst.service.model.BaseTextNoteEvent
|
||||
|
@ -26,7 +28,9 @@ import com.vitorpamplona.amethyst.service.relays.Relay
|
|||
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
|
@ -77,6 +81,11 @@ open class NewPostViewModel() : ViewModel() {
|
|||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
|
||||
// GeoHash
|
||||
var wantsToAddGeoHash by mutableStateOf(false)
|
||||
var locUtil: LocationUtil? = null
|
||||
var location: Flow<String>? = null
|
||||
|
||||
// ZapRaiser
|
||||
var canAddZapRaiser by mutableStateOf(false)
|
||||
var wantsZapraiser by mutableStateOf(false)
|
||||
|
@ -121,6 +130,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||
|
||||
wantsForwardZapTo = false
|
||||
wantsToMarkAsSensitive = false
|
||||
wantsToAddGeoHash = false
|
||||
wantsZapraiser = false
|
||||
zapRaiserAmount = null
|
||||
forwardZapTo = null
|
||||
|
@ -143,6 +153,13 @@ open class NewPostViewModel() : ViewModel() {
|
|||
null
|
||||
}
|
||||
|
||||
val geoLocation = locUtil?.locationStateFlow?.value
|
||||
val geoHash = if (wantsToAddGeoHash && geoLocation != null) {
|
||||
geoLocation.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null
|
||||
|
||||
if (wantsPoll) {
|
||||
|
@ -158,16 +175,17 @@ open class NewPostViewModel() : ViewModel() {
|
|||
zapReceiver,
|
||||
wantsToMarkAsSensitive,
|
||||
localZapRaiserAmount,
|
||||
relayList
|
||||
relayList,
|
||||
geoHash
|
||||
)
|
||||
} else if (originalNote?.channelHex() != null) {
|
||||
if (originalNote is AddressableEvent && originalNote?.address() != null) {
|
||||
account?.sendLiveMessage(tagger.message, originalNote?.address()!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount)
|
||||
account?.sendLiveMessage(tagger.message, originalNote?.address()!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount, geoHash)
|
||||
} else {
|
||||
account?.sendChannelMessage(tagger.message, tagger.channelHex!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount)
|
||||
account?.sendChannelMessage(tagger.message, tagger.channelHex!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount, geoHash)
|
||||
}
|
||||
} else if (originalNote?.event is PrivateDmEvent) {
|
||||
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount)
|
||||
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount, geoHash)
|
||||
} else {
|
||||
// adds markers
|
||||
val rootId =
|
||||
|
@ -187,7 +205,8 @@ open class NewPostViewModel() : ViewModel() {
|
|||
replyingTo = replyId,
|
||||
root = rootId,
|
||||
directMentions = tagger.directMentions,
|
||||
relayList = relayList
|
||||
relayList = relayList,
|
||||
geohash = geoHash
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -267,6 +286,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||
|
||||
wantsForwardZapTo = false
|
||||
wantsToMarkAsSensitive = false
|
||||
wantsToAddGeoHash = false
|
||||
forwardZapTo = null
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
|
||||
|
@ -449,4 +469,50 @@ open class NewPostViewModel() : ViewModel() {
|
|||
fun selectImage(uri: Uri) {
|
||||
contentToAddUrl = uri
|
||||
}
|
||||
|
||||
fun startLocation(context: Context) {
|
||||
locUtil = LocationUtil(context)
|
||||
locUtil?.let {
|
||||
location = it.locationStateFlow.mapLatest {
|
||||
it.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString()
|
||||
}
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
locUtil?.start()
|
||||
}
|
||||
}
|
||||
|
||||
fun stopLocation() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
locUtil?.stop()
|
||||
}
|
||||
location = null
|
||||
locUtil = null
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
locUtil?.stop()
|
||||
}
|
||||
location = null
|
||||
locUtil = null
|
||||
}
|
||||
}
|
||||
|
||||
enum class GeohashPrecision(val digits: Int) {
|
||||
KM_5000_X_5000(1), // 5,000km × 5,000km
|
||||
KM_1250_X_625(2), // 1,250km × 625km
|
||||
KM_156_X_156(3), // 156km × 156km
|
||||
KM_39_X_19(4), // 39.1km × 19.5km
|
||||
KM_5_X_5(5), // 4.89km × 4.89km
|
||||
|
||||
M_1000_X_600(6), // 1.22km × 0.61km
|
||||
M_153_X_153(7), // 153m × 153m
|
||||
M_38_X_19(8), // 38.2m × 19.1m
|
||||
M_5_X_5(9), // 4.77m × 4.77m
|
||||
|
||||
MM_1000_X_1000(10), // 1.19m × 0.596m
|
||||
MM_149_X_149(11), // 149mm × 149mm
|
||||
MM_37_X_18(12) // 37.2mm × 18.6mm
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
|||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun InvoiceRequest(
|
||||
fun InvoiceRequestCard(
|
||||
lud16: String,
|
||||
toUserPubKeyHex: String,
|
||||
account: Account,
|
||||
|
@ -51,9 +51,6 @@ fun InvoiceRequest(
|
|||
onSuccess: (String) -> Unit,
|
||||
onClose: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
@ -66,102 +63,118 @@ fun InvoiceRequest(
|
|||
.fillMaxWidth()
|
||||
.padding(30.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.lightning),
|
||||
null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
|
||||
Text(
|
||||
text = titleText ?: stringResource(R.string.lightning_tips),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
var message by remember { mutableStateOf("") }
|
||||
var amount by remember { mutableStateOf(1000L) }
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.note_to_receiver)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = message,
|
||||
onValueChange = { message = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.thank_you_so_much),
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.amount_in_sats)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = amount.toString(),
|
||||
onValueChange = {
|
||||
runCatching {
|
||||
if (it.isEmpty()) {
|
||||
amount = 0
|
||||
} else {
|
||||
amount = it.toLong()
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "1000",
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number
|
||||
),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp),
|
||||
onClick = {
|
||||
val zapRequest = account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType)
|
||||
|
||||
LightningAddressResolver().lnAddressInvoice(
|
||||
lud16,
|
||||
amount * 1000,
|
||||
message,
|
||||
zapRequest?.toJson(),
|
||||
onSuccess = onSuccess,
|
||||
onError = {
|
||||
scope.launch {
|
||||
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
||||
onClose()
|
||||
}
|
||||
},
|
||||
onProgress = {
|
||||
}
|
||||
)
|
||||
},
|
||||
shape = QuoteBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
) {
|
||||
Text(text = buttonText ?: stringResource(R.string.send_sats), color = Color.White, fontSize = 20.sp)
|
||||
}
|
||||
InvoiceRequest(lud16, toUserPubKeyHex, account, titleText, buttonText, onSuccess, onClose)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InvoiceRequest(
|
||||
lud16: String,
|
||||
toUserPubKeyHex: String,
|
||||
account: Account,
|
||||
titleText: String? = null,
|
||||
buttonText: String? = null,
|
||||
onSuccess: (String) -> Unit,
|
||||
onClose: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.lightning),
|
||||
null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
|
||||
Text(
|
||||
text = titleText ?: stringResource(R.string.lightning_tips),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
var message by remember { mutableStateOf("") }
|
||||
var amount by remember { mutableStateOf(1000L) }
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.note_to_receiver)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = message,
|
||||
onValueChange = { message = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.thank_you_so_much),
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences
|
||||
),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.amount_in_sats)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = amount.toString(),
|
||||
onValueChange = {
|
||||
runCatching {
|
||||
if (it.isEmpty()) {
|
||||
amount = 0
|
||||
} else {
|
||||
amount = it.toLong()
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "1000",
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number
|
||||
),
|
||||
singleLine = true
|
||||
)
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp),
|
||||
onClick = {
|
||||
val zapRequest = account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType)
|
||||
|
||||
LightningAddressResolver().lnAddressInvoice(
|
||||
lud16,
|
||||
amount * 1000,
|
||||
message,
|
||||
zapRequest?.toJson(),
|
||||
onSuccess = onSuccess,
|
||||
onError = {
|
||||
scope.launch {
|
||||
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
||||
onClose()
|
||||
}
|
||||
},
|
||||
onProgress = {
|
||||
}
|
||||
)
|
||||
},
|
||||
shape = QuoteBorder,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
)
|
||||
) {
|
||||
Text(text = buttonText ?: stringResource(R.string.send_sats), color = Color.White, fontSize = 20.sp)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,66 +35,62 @@ fun ZapRaiserRequest(
|
|||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 10.dp)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.lightning),
|
||||
null,
|
||||
modifier = Size20Modifier,
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
|
||||
Text(
|
||||
text = titleText ?: stringResource(R.string.zapraiser),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.lightning),
|
||||
null,
|
||||
modifier = Size20Modifier,
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.zapraiser_explainer),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(vertical = 10.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.zapraiser_target_amount_in_sats)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = if (newPostViewModel.zapRaiserAmount != null) {
|
||||
newPostViewModel.zapRaiserAmount.toString()
|
||||
} else {
|
||||
""
|
||||
},
|
||||
onValueChange = {
|
||||
runCatching {
|
||||
if (it.isEmpty()) {
|
||||
newPostViewModel.zapRaiserAmount = null
|
||||
} else {
|
||||
newPostViewModel.zapRaiserAmount = it.toLongOrNull()
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "1000",
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number
|
||||
),
|
||||
singleLine = true
|
||||
text = titleText ?: stringResource(R.string.zapraiser),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.zapraiser_explainer),
|
||||
color = MaterialTheme.colors.placeholderText,
|
||||
modifier = Modifier.padding(vertical = 10.dp)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringResource(R.string.zapraiser_target_amount_in_sats)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = if (newPostViewModel.zapRaiserAmount != null) {
|
||||
newPostViewModel.zapRaiserAmount.toString()
|
||||
} else {
|
||||
""
|
||||
},
|
||||
onValueChange = {
|
||||
runCatching {
|
||||
if (it.isEmpty()) {
|
||||
newPostViewModel.zapRaiserAmount = null
|
||||
} else {
|
||||
newPostViewModel.zapRaiserAmount = it.toLongOrNull()
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "1000",
|
||||
color = MaterialTheme.colors.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number
|
||||
),
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ import androidx.lifecycle.map
|
|||
import coil.compose.AsyncImage
|
||||
import coil.compose.AsyncImagePainter
|
||||
import coil.request.SuccessResult
|
||||
import com.fonfon.kgeohash.toGeoHash
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
|
@ -83,6 +84,7 @@ import com.vitorpamplona.amethyst.model.Note
|
|||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.model.UserMetadata
|
||||
import com.vitorpamplona.amethyst.service.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
||||
import com.vitorpamplona.amethyst.service.model.ATag
|
||||
import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent
|
||||
|
@ -2405,6 +2407,11 @@ fun SecondUserInfoRow(
|
|||
Row(verticalAlignment = CenterVertically, modifier = UserNameMaxRowHeight) {
|
||||
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) })
|
||||
|
||||
val geo = remember { noteEvent.getGeoHash() }
|
||||
if (geo != null) {
|
||||
DisplayLocation(geo)
|
||||
}
|
||||
|
||||
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
|
||||
if (baseReward != null) {
|
||||
DisplayReward(baseReward, note, accountViewModel, nav)
|
||||
|
@ -2417,6 +2424,22 @@ fun SecondUserInfoRow(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayLocation(geohash: String) {
|
||||
val context = LocalContext.current
|
||||
val cityName = remember(geohash) {
|
||||
ReverseGeoLocationUtil().execute(geohash.toGeoHash().toLocation(), context)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = cityName ?: geohash,
|
||||
color = MaterialTheme.colors.lessImportantLink,
|
||||
fontSize = Font14SP,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FirstUserInfoRow(
|
||||
baseNote: Note,
|
||||
|
|
|
@ -71,7 +71,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
|||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
|
||||
import com.vitorpamplona.amethyst.ui.components.InvoiceRequest
|
||||
import com.vitorpamplona.amethyst.ui.components.InvoiceRequestCard
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
|
@ -1002,7 +1002,7 @@ fun DisplayLNAddress(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 5.dp)
|
||||
) {
|
||||
InvoiceRequest(
|
||||
InvoiceRequestCard(
|
||||
lud16,
|
||||
userHex,
|
||||
account,
|
||||
|
|
|
@ -46,6 +46,7 @@ val DoubleVertSpacer = Modifier.height(10.dp)
|
|||
val HalfDoubleVertSpacer = Modifier.height(7.dp)
|
||||
|
||||
val Size0dp = 0.dp
|
||||
val Size5dp = 5.dp
|
||||
val Size10dp = 10.dp
|
||||
val Size13dp = 13.dp
|
||||
val Size15dp = 15.dp
|
||||
|
|
|
@ -508,4 +508,12 @@
|
|||
<string name="nip05_checking">Checking Nostr address</string>
|
||||
<string name="select_deselect_all">Select/Deselect all</string>
|
||||
<string name="default_relays">Default</string>
|
||||
|
||||
<string name="zap_forward_title">Forward Zaps to:</string>
|
||||
<string name="zap_forward_explainer">Supporting clients will forward zaps to the LNAddress or User Profile below instead of yours</string>
|
||||
|
||||
<string name="geohash_title">Expose Location as </string>
|
||||
<string name="geohash_explainer">Adds a Geohash of your location to the post. People will know you are within 5x5km (3x3mi) or this hash</string>
|
||||
|
||||
<string name="add_sensitive_content_explainer">Adds sensitive content warning before showing your content. This is ideal for any NSFW content or content some people may find offensive or disturbing</string>
|
||||
</resources>
|
||||
|
|
Ładowanie…
Reference in New Issue