diff --git a/app/build.gradle b/app/build.gradle
index 9fa87a677..40ce5c349 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 00767122f..2031bb209 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -37,6 +37,9 @@
+
+
+
,
- relayList: List? = null
+ relayList: List? = 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? = null
+ relayList: List? = 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?, mentions: List?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) {
+ fun sendChannelMessage(message: String, toChannel: String, replyTo: List?, mentions: List?, 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?, mentions: List?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) {
+ fun sendLiveMessage(message: String, toChannel: ATag, replyTo: List?, mentions: List?, 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?, zapReceiver: String? = null, wantsToMarkAsSensitive: Boolean, zapRaiserAmount: Long? = null) {
+ fun sendPrivateMessage(message: String, toUser: User, replyingTo: Note? = null, mentions: List?, 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
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt
new file mode 100644
index 000000000..cfda4b271
--- /dev/null
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt
@@ -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(LocationManager.NETWORK_PROVIDER))
+ val providerState = mutableStateOf(false)
+ val isStart: MutableState = 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
+ }
+ }
+}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt
index ca0f22295..c38eed35e 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ChannelMessageEvent.kt
@@ -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)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
index 857a30ebc..59e015410 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt
@@ -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) }
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt
index 7087d183a..39a6850ec 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventInterface.kt
@@ -45,6 +45,7 @@ interface EventInterface {
fun getReward(): BigDecimal?
fun getPoWRank(): Int
+ fun getGeoHash(): String?
fun zapAddress(): String?
fun isSensitive(): Boolean
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LiveActivitiesChatMessageEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LiveActivitiesChatMessageEvent.kt
index dd925bb47..4b85e0c33 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LiveActivitiesChatMessageEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LiveActivitiesChatMessageEvent.kt
@@ -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)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt
index 681910a0a..3eb33b5be 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PollNoteEvent.kt
@@ -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>()
@@ -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)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
index b3ade2392..c1d41e845 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/PrivateDmEvent.kt
@@ -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())
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt
index 9e2be3b9b..5037fa0b1 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt
@@ -38,6 +38,7 @@ class TextNoteEvent(
replyingTo: String?,
root: String?,
directMentions: Set,
+ 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)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt
index 0aa09784e..6aed33483 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt
@@ -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?>(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) {
+ 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?, 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()
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
index 8036a580e..85332583f 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt
@@ -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? = 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
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt
index 6af52b5fd..d83a8b9db 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt
@@ -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)
+ }
+}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt
index 6fa836980..378e60725 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZapRaiserRequest.kt
@@ -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
+ )
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt
index a66792b96..0928f0f20 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt
@@ -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,
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
index a7ef336d7..aac3c8cb7 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
@@ -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,
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt
index a7fd7118f..f6f1e3f42 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt
@@ -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
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7494502b8..cb4bea068 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -508,4 +508,12 @@
Checking Nostr address
Select/Deselect all
Default
+
+ Forward Zaps to:
+ Supporting clients will forward zaps to the LNAddress or User Profile below instead of yours
+
+ Expose Location as
+ Adds a Geohash of your location to the post. People will know you are within 5x5km (3x3mi) or this hash
+
+ Adds sensitive content warning before showing your content. This is ideal for any NSFW content or content some people may find offensive or disturbing