- Adds support for GeoHash
- Refactors New Post Buttons to make them more similar to one another.
This commit is contained in:
@@ -191,6 +191,9 @@ dependencies {
|
|||||||
// immutable collections to avoid recomposition
|
// immutable collections to avoid recomposition
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
|
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
|
||||||
|
|
||||||
|
// GeoHash
|
||||||
|
implementation 'com.github.drfonfon:android-kotlin-geohash:1.0'
|
||||||
|
|
||||||
// Video compression lib
|
// Video compression lib
|
||||||
implementation 'com.github.AbedElazizShe:LightCompressor:1.3.1'
|
implementation 'com.github.AbedElazizShe:LightCompressor:1.3.1'
|
||||||
// Image compression lib
|
// Image compression lib
|
||||||
|
|||||||
@@ -37,6 +37,9 @@
|
|||||||
<!-- This notification permission is needed for some phones -->
|
<!-- This notification permission is needed for some phones -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<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 -->
|
<!-- Old permission to access media -->
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
|||||||
@@ -671,7 +671,8 @@ class Account(
|
|||||||
replyingTo: String?,
|
replyingTo: String?,
|
||||||
root: String?,
|
root: String?,
|
||||||
directMentions: Set<HexKey>,
|
directMentions: Set<HexKey>,
|
||||||
relayList: List<Relay>? = null
|
relayList: List<Relay>? = null,
|
||||||
|
geohash: String? = null
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -691,6 +692,7 @@ class Account(
|
|||||||
replyingTo = replyingTo,
|
replyingTo = replyingTo,
|
||||||
root = root,
|
root = root,
|
||||||
directMentions = directMentions,
|
directMentions = directMentions,
|
||||||
|
geohash = geohash,
|
||||||
privateKey = keyPair.privKey!!
|
privateKey = keyPair.privKey!!
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -710,7 +712,8 @@ class Account(
|
|||||||
zapReceiver: String? = null,
|
zapReceiver: String? = null,
|
||||||
wantsToMarkAsSensitive: Boolean,
|
wantsToMarkAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
relayList: List<Relay>? = null
|
relayList: List<Relay>? = null,
|
||||||
|
geohash: String? = null
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -731,14 +734,15 @@ class Account(
|
|||||||
closedAt = closedAt,
|
closedAt = closedAt,
|
||||||
zapReceiver = zapReceiver,
|
zapReceiver = zapReceiver,
|
||||||
markAsSensitive = wantsToMarkAsSensitive,
|
markAsSensitive = wantsToMarkAsSensitive,
|
||||||
zapRaiserAmount = zapRaiserAmount
|
zapRaiserAmount = zapRaiserAmount,
|
||||||
|
geohash = geohash
|
||||||
)
|
)
|
||||||
// println("Sending new PollNoteEvent: %s".format(signedEvent.toJson()))
|
// println("Sending new PollNoteEvent: %s".format(signedEvent.toJson()))
|
||||||
Client.send(signedEvent, relayList = relayList)
|
Client.send(signedEvent, relayList = relayList)
|
||||||
LocalCache.consume(signedEvent)
|
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
|
if (!isWriteable()) return
|
||||||
|
|
||||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
@@ -753,13 +757,14 @@ class Account(
|
|||||||
zapReceiver = zapReceiver,
|
zapReceiver = zapReceiver,
|
||||||
markAsSensitive = wantsToMarkAsSensitive,
|
markAsSensitive = wantsToMarkAsSensitive,
|
||||||
zapRaiserAmount = zapRaiserAmount,
|
zapRaiserAmount = zapRaiserAmount,
|
||||||
|
geohash = geohash,
|
||||||
privateKey = keyPair.privKey!!
|
privateKey = keyPair.privKey!!
|
||||||
)
|
)
|
||||||
Client.send(signedEvent)
|
Client.send(signedEvent)
|
||||||
LocalCache.consume(signedEvent, null)
|
LocalCache.consume(signedEvent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendLiveMessage(message: String, toChannel: ATag, replyTo: List<Note>?, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) {
|
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
|
if (!isWriteable()) return
|
||||||
|
|
||||||
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
// val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
@@ -774,13 +779,14 @@ class Account(
|
|||||||
zapReceiver = zapReceiver,
|
zapReceiver = zapReceiver,
|
||||||
markAsSensitive = wantsToMarkAsSensitive,
|
markAsSensitive = wantsToMarkAsSensitive,
|
||||||
zapRaiserAmount = zapRaiserAmount,
|
zapRaiserAmount = zapRaiserAmount,
|
||||||
|
geohash = geohash,
|
||||||
privateKey = keyPair.privKey!!
|
privateKey = keyPair.privKey!!
|
||||||
)
|
)
|
||||||
Client.send(signedEvent)
|
Client.send(signedEvent)
|
||||||
LocalCache.consume(signedEvent, null)
|
LocalCache.consume(signedEvent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List<User>?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) {
|
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
|
if (!isWriteable()) return
|
||||||
|
|
||||||
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
@@ -795,6 +801,7 @@ class Account(
|
|||||||
zapReceiver = zapReceiver,
|
zapReceiver = zapReceiver,
|
||||||
markAsSensitive = wantsToMarkAsSensitive,
|
markAsSensitive = wantsToMarkAsSensitive,
|
||||||
zapRaiserAmount = zapRaiserAmount,
|
zapRaiserAmount = zapRaiserAmount,
|
||||||
|
geohash = geohash,
|
||||||
privateKey = keyPair.privKey!!,
|
privateKey = keyPair.privKey!!,
|
||||||
advertiseNip18 = false
|
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,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long?
|
zapRaiserAmount: Long?,
|
||||||
|
geohash: String? = null
|
||||||
): ChannelMessageEvent {
|
): ChannelMessageEvent {
|
||||||
val content = message
|
val content = message
|
||||||
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
||||||
@@ -58,6 +59,9 @@ class ChannelMessageEvent(
|
|||||||
zapRaiserAmount?.let {
|
zapRaiserAmount?.let {
|
||||||
tags.add(listOf("zapraiser", "$it"))
|
tags.add(listOf("zapraiser", "$it"))
|
||||||
}
|
}
|
||||||
|
geohash?.let {
|
||||||
|
tags.add(listOf("g", it))
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = CryptoUtils.sign(id, privateKey)
|
val sig = CryptoUtils.sign(id, privateKey)
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ open class Event(
|
|||||||
return rank
|
return rank
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getGeoHash(): String? {
|
||||||
|
return tags.firstOrNull { it.size > 1 && it[0] == "g" }?.get(1)?.ifBlank { null }
|
||||||
|
}
|
||||||
|
|
||||||
override fun getReward(): BigDecimal? {
|
override fun getReward(): BigDecimal? {
|
||||||
return try {
|
return try {
|
||||||
tags.firstOrNull { it.size > 1 && it[0] == "reward" }?.get(1)?.let { BigDecimal(it) }
|
tags.firstOrNull { it.size > 1 && it[0] == "reward" }?.get(1)?.let { BigDecimal(it) }
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ interface EventInterface {
|
|||||||
|
|
||||||
fun getReward(): BigDecimal?
|
fun getReward(): BigDecimal?
|
||||||
fun getPoWRank(): Int
|
fun getPoWRank(): Int
|
||||||
|
fun getGeoHash(): String?
|
||||||
|
|
||||||
fun zapAddress(): String?
|
fun zapAddress(): String?
|
||||||
fun isSensitive(): Boolean
|
fun isSensitive(): Boolean
|
||||||
|
|||||||
+5
-1
@@ -51,7 +51,8 @@ class LiveActivitiesChatMessageEvent(
|
|||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long?
|
zapRaiserAmount: Long?,
|
||||||
|
geohash: String? = null
|
||||||
): LiveActivitiesChatMessageEvent {
|
): LiveActivitiesChatMessageEvent {
|
||||||
val content = message
|
val content = message
|
||||||
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
||||||
@@ -73,6 +74,9 @@ class LiveActivitiesChatMessageEvent(
|
|||||||
zapRaiserAmount?.let {
|
zapRaiserAmount?.let {
|
||||||
tags.add(listOf("zapraiser", "$it"))
|
tags.add(listOf("zapraiser", "$it"))
|
||||||
}
|
}
|
||||||
|
geohash?.let {
|
||||||
|
tags.add(listOf("g", it))
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = CryptoUtils.sign(id, privateKey)
|
val sig = CryptoUtils.sign(id, privateKey)
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ class PollNoteEvent(
|
|||||||
closedAt: Int?,
|
closedAt: Int?,
|
||||||
zapReceiver: String?,
|
zapReceiver: String?,
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long?
|
zapRaiserAmount: Long?,
|
||||||
|
geohash: String? = null
|
||||||
): PollNoteEvent {
|
): PollNoteEvent {
|
||||||
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
@@ -83,6 +84,9 @@ class PollNoteEvent(
|
|||||||
zapRaiserAmount?.let {
|
zapRaiserAmount?.let {
|
||||||
tags.add(listOf("zapraiser", "$it"))
|
tags.add(listOf("zapraiser", "$it"))
|
||||||
}
|
}
|
||||||
|
geohash?.let {
|
||||||
|
tags.add(listOf("g", it))
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||||
val sig = CryptoUtils.sign(id, privateKey)
|
val sig = CryptoUtils.sign(id, privateKey)
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class PrivateDmEvent(
|
|||||||
publishedRecipientPubKey: ByteArray? = null,
|
publishedRecipientPubKey: ByteArray? = null,
|
||||||
advertiseNip18: Boolean = true,
|
advertiseNip18: Boolean = true,
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long?
|
zapRaiserAmount: Long?,
|
||||||
|
geohash: String? = null
|
||||||
): PrivateDmEvent {
|
): PrivateDmEvent {
|
||||||
val content = CryptoUtils.encrypt(
|
val content = CryptoUtils.encrypt(
|
||||||
if (advertiseNip18) { nip18Advertisement } else { "" } + msg,
|
if (advertiseNip18) { nip18Advertisement } else { "" } + msg,
|
||||||
@@ -113,6 +114,10 @@ class PrivateDmEvent(
|
|||||||
zapRaiserAmount?.let {
|
zapRaiserAmount?.let {
|
||||||
tags.add(listOf("zapraiser", "$it"))
|
tags.add(listOf("zapraiser", "$it"))
|
||||||
}
|
}
|
||||||
|
geohash?.let {
|
||||||
|
tags.add(listOf("g", it))
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = CryptoUtils.sign(id, privateKey)
|
val sig = CryptoUtils.sign(id, privateKey)
|
||||||
return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
return PrivateDmEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class TextNoteEvent(
|
|||||||
replyingTo: String?,
|
replyingTo: String?,
|
||||||
root: String?,
|
root: String?,
|
||||||
directMentions: Set<HexKey>,
|
directMentions: Set<HexKey>,
|
||||||
|
geohash: String? = null,
|
||||||
|
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = TimeUtils.now()
|
createdAt: Long = TimeUtils.now()
|
||||||
@@ -93,6 +94,9 @@ class TextNoteEvent(
|
|||||||
zapRaiserAmount?.let {
|
zapRaiserAmount?.let {
|
||||||
tags.add(listOf("zapraiser", "$it"))
|
tags.add(listOf("zapraiser", "$it"))
|
||||||
}
|
}
|
||||||
|
geohash?.let {
|
||||||
|
tags.add(listOf("g", it))
|
||||||
|
}
|
||||||
|
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
val id = generateId(pubKey, createdAt, kind, tags, msg)
|
||||||
val sig = CryptoUtils.sign(id, privateKey)
|
val sig = CryptoUtils.sign(id, privateKey)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
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.ArrowForwardIos
|
||||||
import androidx.compose.material.icons.filled.Bolt
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
import androidx.compose.material.icons.filled.CurrencyBitcoin
|
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.ShowChart
|
||||||
import androidx.compose.material.icons.filled.Visibility
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
import androidx.compose.material.icons.filled.VisibilityOff
|
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.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -66,10 +70,15 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import coil.compose.AsyncImage
|
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.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||||
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
|
||||||
import com.vitorpamplona.amethyst.ui.components.*
|
import com.vitorpamplona.amethyst.ui.components.*
|
||||||
import com.vitorpamplona.amethyst.ui.note.CancelIcon
|
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.screen.loggedIn.UserLine
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||||
@@ -92,6 +102,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@@ -278,28 +289,72 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (postViewModel.wantsPoll) {
|
if (postViewModel.wantsPoll) {
|
||||||
postViewModel.pollOptions.values.forEachIndexed { index, _ ->
|
Row(
|
||||||
NewPollOption(postViewModel, index)
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
}
|
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = { postViewModel.pollOptions[postViewModel.pollOptions.size] = "" },
|
|
||||||
border = BorderStroke(1.dp, MaterialTheme.colors.placeholderText),
|
|
||||||
colors = ButtonDefaults.outlinedButtonColors(
|
|
||||||
contentColor = MaterialTheme.colors.placeholderText
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Image(
|
Column(
|
||||||
painterResource(id = android.R.drawable.ic_input_add),
|
modifier = Modifier.fillMaxWidth()
|
||||||
contentDescription = "Add poll option button",
|
) {
|
||||||
modifier = Modifier.size(18.dp)
|
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
|
val url = postViewModel.contentToAddUrl
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||||
ImageVideoDescription(
|
ImageVideoDescription(
|
||||||
url,
|
url,
|
||||||
account.defaultFileServer,
|
account.defaultFileServer,
|
||||||
@@ -324,26 +379,28 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
val lud16 = user?.info?.lnAddress()
|
val lud16 = user?.info?.lnAddress()
|
||||||
|
|
||||||
if (lud16 != null && postViewModel.wantsInvoice) {
|
if (lud16 != null && postViewModel.wantsInvoice) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = 5.dp)) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||||
InvoiceRequest(
|
Column(Modifier.fillMaxWidth()) {
|
||||||
lud16,
|
InvoiceRequest(
|
||||||
user.pubkeyHex,
|
lud16,
|
||||||
account,
|
user.pubkeyHex,
|
||||||
stringResource(id = R.string.lightning_invoice),
|
account,
|
||||||
stringResource(id = R.string.lightning_create_and_add_invoice),
|
stringResource(id = R.string.lightning_invoice),
|
||||||
onSuccess = {
|
stringResource(id = R.string.lightning_create_and_add_invoice),
|
||||||
postViewModel.message = TextFieldValue(postViewModel.message.text + "\n\n" + it)
|
onSuccess = {
|
||||||
postViewModel.wantsInvoice = false
|
postViewModel.message = TextFieldValue(postViewModel.message.text + "\n\n" + it)
|
||||||
},
|
postViewModel.wantsInvoice = false
|
||||||
onClose = {
|
},
|
||||||
postViewModel.wantsInvoice = false
|
onClose = {
|
||||||
}
|
postViewModel.wantsInvoice = false
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lud16 != null && postViewModel.wantsZapraiser) {
|
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(
|
ZapRaiserRequest(
|
||||||
stringResource(id = R.string.zapraiser),
|
stringResource(id = R.string.zapraiser),
|
||||||
postViewModel
|
postViewModel
|
||||||
@@ -353,7 +410,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
|
|
||||||
val myUrlPreview = postViewModel.urlPreview
|
val myUrlPreview = postViewModel.urlPreview
|
||||||
if (myUrlPreview != null) {
|
if (myUrlPreview != null) {
|
||||||
Row(modifier = Modifier.padding(top = 5.dp)) {
|
Row(modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
|
||||||
if (isValidURL(myUrlPreview)) {
|
if (isValidURL(myUrlPreview)) {
|
||||||
val removedParamsFromUrl =
|
val removedParamsFromUrl =
|
||||||
myUrlPreview.split("?")[0].lowercase()
|
myUrlPreview.split("?")[0].lowercase()
|
||||||
@@ -456,6 +513,10 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive
|
postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddGeoHash(postViewModel) {
|
||||||
|
postViewModel.wantsToAddGeoHash = !postViewModel.wantsToAddGeoHash
|
||||||
|
}
|
||||||
|
|
||||||
ForwardZapTo(postViewModel) {
|
ForwardZapTo(postViewModel) {
|
||||||
postViewModel.wantsForwardZapTo = !postViewModel.wantsForwardZapTo
|
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)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Notifying(baseMentions: ImmutableList<User>?, onClick: (User) -> Unit) {
|
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
|
@Composable
|
||||||
private fun AddLnInvoiceButton(
|
private fun AddLnInvoiceButton(
|
||||||
isLnInvoiceActive: Boolean,
|
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
|
@Composable
|
||||||
@@ -913,7 +1193,9 @@ fun ImageVideoDescription(
|
|||||||
)
|
)
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(30.dp).padding(end = 5.dp),
|
modifier = Modifier
|
||||||
|
.size(30.dp)
|
||||||
|
.padding(end = 5.dp),
|
||||||
onClick = onCancel
|
onClick = onCancel
|
||||||
) {
|
) {
|
||||||
CancelIcon()
|
CancelIcon()
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import androidx.compose.ui.text.TextRange
|
|||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.fonfon.kgeohash.toGeoHash
|
||||||
import com.vitorpamplona.amethyst.model.*
|
import com.vitorpamplona.amethyst.model.*
|
||||||
import com.vitorpamplona.amethyst.service.FileHeader
|
import com.vitorpamplona.amethyst.service.FileHeader
|
||||||
|
import com.vitorpamplona.amethyst.service.LocationUtil
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.AddressableEvent
|
import com.vitorpamplona.amethyst.service.model.AddressableEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.BaseTextNoteEvent
|
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.MediaCompressor
|
||||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@@ -77,6 +81,11 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
// NSFW, Sensitive
|
// NSFW, Sensitive
|
||||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||||
|
|
||||||
|
// GeoHash
|
||||||
|
var wantsToAddGeoHash by mutableStateOf(false)
|
||||||
|
var locUtil: LocationUtil? = null
|
||||||
|
var location: Flow<String>? = null
|
||||||
|
|
||||||
// ZapRaiser
|
// ZapRaiser
|
||||||
var canAddZapRaiser by mutableStateOf(false)
|
var canAddZapRaiser by mutableStateOf(false)
|
||||||
var wantsZapraiser by mutableStateOf(false)
|
var wantsZapraiser by mutableStateOf(false)
|
||||||
@@ -121,6 +130,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
|
|
||||||
wantsForwardZapTo = false
|
wantsForwardZapTo = false
|
||||||
wantsToMarkAsSensitive = false
|
wantsToMarkAsSensitive = false
|
||||||
|
wantsToAddGeoHash = false
|
||||||
wantsZapraiser = false
|
wantsZapraiser = false
|
||||||
zapRaiserAmount = null
|
zapRaiserAmount = null
|
||||||
forwardZapTo = null
|
forwardZapTo = null
|
||||||
@@ -143,6 +153,13 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
null
|
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
|
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null
|
||||||
|
|
||||||
if (wantsPoll) {
|
if (wantsPoll) {
|
||||||
@@ -158,16 +175,17 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
zapReceiver,
|
zapReceiver,
|
||||||
wantsToMarkAsSensitive,
|
wantsToMarkAsSensitive,
|
||||||
localZapRaiserAmount,
|
localZapRaiserAmount,
|
||||||
relayList
|
relayList,
|
||||||
|
geoHash
|
||||||
)
|
)
|
||||||
} else if (originalNote?.channelHex() != null) {
|
} else if (originalNote?.channelHex() != null) {
|
||||||
if (originalNote is AddressableEvent && originalNote?.address() != 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 {
|
} 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) {
|
} 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 {
|
} else {
|
||||||
// adds markers
|
// adds markers
|
||||||
val rootId =
|
val rootId =
|
||||||
@@ -187,7 +205,8 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
replyingTo = replyId,
|
replyingTo = replyId,
|
||||||
root = rootId,
|
root = rootId,
|
||||||
directMentions = tagger.directMentions,
|
directMentions = tagger.directMentions,
|
||||||
relayList = relayList
|
relayList = relayList,
|
||||||
|
geohash = geoHash
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +286,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
|
|
||||||
wantsForwardZapTo = false
|
wantsForwardZapTo = false
|
||||||
wantsToMarkAsSensitive = false
|
wantsToMarkAsSensitive = false
|
||||||
|
wantsToAddGeoHash = false
|
||||||
forwardZapTo = null
|
forwardZapTo = null
|
||||||
forwardZapToEditting = TextFieldValue("")
|
forwardZapToEditting = TextFieldValue("")
|
||||||
|
|
||||||
@@ -449,4 +469,50 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
fun selectImage(uri: Uri) {
|
fun selectImage(uri: Uri) {
|
||||||
contentToAddUrl = 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
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InvoiceRequest(
|
fun InvoiceRequestCard(
|
||||||
lud16: String,
|
lud16: String,
|
||||||
toUserPubKeyHex: String,
|
toUserPubKeyHex: String,
|
||||||
account: Account,
|
account: Account,
|
||||||
@@ -51,9 +51,6 @@ fun InvoiceRequest(
|
|||||||
onSuccess: (String) -> Unit,
|
onSuccess: (String) -> Unit,
|
||||||
onClose: () -> Unit
|
onClose: () -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -66,102 +63,118 @@ fun InvoiceRequest(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(30.dp)
|
.padding(30.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
InvoiceRequest(lud16, toUserPubKeyHex, account, titleText, buttonText, onSuccess, onClose)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth()
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 10.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Icon(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
painter = painterResource(R.drawable.lightning),
|
||||||
modifier = Modifier
|
null,
|
||||||
.fillMaxWidth()
|
modifier = Size20Modifier,
|
||||||
.padding(bottom = 10.dp)
|
tint = Color.Unspecified
|
||||||
) {
|
)
|
||||||
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()
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.zapraiser_explainer),
|
text = titleText ?: stringResource(R.string.zapraiser),
|
||||||
color = MaterialTheme.colors.placeholderText,
|
fontSize = 20.sp,
|
||||||
modifier = Modifier.padding(vertical = 10.dp)
|
fontWeight = FontWeight.W500,
|
||||||
)
|
modifier = Modifier.padding(start = 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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil.compose.AsyncImagePainter
|
||||||
import coil.request.SuccessResult
|
import coil.request.SuccessResult
|
||||||
|
import com.fonfon.kgeohash.toGeoHash
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
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.User
|
||||||
import com.vitorpamplona.amethyst.model.UserMetadata
|
import com.vitorpamplona.amethyst.model.UserMetadata
|
||||||
import com.vitorpamplona.amethyst.service.OnlineChecker
|
import com.vitorpamplona.amethyst.service.OnlineChecker
|
||||||
|
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
||||||
import com.vitorpamplona.amethyst.service.model.ATag
|
import com.vitorpamplona.amethyst.service.model.ATag
|
||||||
import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent
|
import com.vitorpamplona.amethyst.service.model.AppDefinitionEvent
|
||||||
@@ -2405,6 +2407,11 @@ fun SecondUserInfoRow(
|
|||||||
Row(verticalAlignment = CenterVertically, modifier = UserNameMaxRowHeight) {
|
Row(verticalAlignment = CenterVertically, modifier = UserNameMaxRowHeight) {
|
||||||
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) })
|
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) })
|
||||||
|
|
||||||
|
val geo = remember { noteEvent.getGeoHash() }
|
||||||
|
if (geo != null) {
|
||||||
|
DisplayLocation(geo)
|
||||||
|
}
|
||||||
|
|
||||||
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
|
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
|
||||||
if (baseReward != null) {
|
if (baseReward != null) {
|
||||||
DisplayReward(baseReward, note, accountViewModel, nav)
|
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
|
@Composable
|
||||||
fun FirstUserInfoRow(
|
fun FirstUserInfoRow(
|
||||||
baseNote: Note,
|
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.actions.toImmutableListOfLists
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
|
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.RobohashAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
@@ -1002,7 +1002,7 @@ fun DisplayLNAddress(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(vertical = 5.dp)
|
modifier = Modifier.padding(vertical = 5.dp)
|
||||||
) {
|
) {
|
||||||
InvoiceRequest(
|
InvoiceRequestCard(
|
||||||
lud16,
|
lud16,
|
||||||
userHex,
|
userHex,
|
||||||
account,
|
account,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ val DoubleVertSpacer = Modifier.height(10.dp)
|
|||||||
val HalfDoubleVertSpacer = Modifier.height(7.dp)
|
val HalfDoubleVertSpacer = Modifier.height(7.dp)
|
||||||
|
|
||||||
val Size0dp = 0.dp
|
val Size0dp = 0.dp
|
||||||
|
val Size5dp = 5.dp
|
||||||
val Size10dp = 10.dp
|
val Size10dp = 10.dp
|
||||||
val Size13dp = 13.dp
|
val Size13dp = 13.dp
|
||||||
val Size15dp = 15.dp
|
val Size15dp = 15.dp
|
||||||
|
|||||||
@@ -508,4 +508,12 @@
|
|||||||
<string name="nip05_checking">Checking Nostr address</string>
|
<string name="nip05_checking">Checking Nostr address</string>
|
||||||
<string name="select_deselect_all">Select/Deselect all</string>
|
<string name="select_deselect_all">Select/Deselect all</string>
|
||||||
<string name="default_relays">Default</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>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user