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