From 5f459ac924284aebc0293abd5b1bb4e39350b10d Mon Sep 17 00:00:00 2001 From: Believethehype Date: Sun, 26 Mar 2023 23:57:25 +0200 Subject: [PATCH 01/27] Custom Messages for Zaps - Includes adding a custom message to sats in Backend Code - Added a simple window on double tap on the bolt symbol for custom amount + message - ! The popup might need some beautification - ! Messages are not shown yet in Amethyst - ! Window could provide options for non-zaps and private zaps in the future --- .../vitorpamplona/amethyst/model/Account.kt | 8 +- .../service/model/LnZapRequestEvent.kt | 6 +- .../amethyst/ui/components/InvoiceRequest.kt | 2 +- .../amethyst/ui/note/ReactionsRow.kt | 18 +- .../amethyst/ui/note/ZapOptionsDialog.kt | 180 ++++++++++++++++++ .../ui/screen/loggedIn/AccountViewModel.kt | 2 +- 6 files changed, 203 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapOptionsDialog.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 0e2c36ccd..7ca3323eb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -151,11 +151,11 @@ class Account( } } - fun createZapRequestFor(note: Note): LnZapRequestEvent? { + fun createZapRequestFor(note: Note, message: String = ""): LnZapRequestEvent? { if (!isWriteable()) return null note.event?.let { - return LnZapRequestEvent.create(it, userProfile().latestContactList?.relays()?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!) + return LnZapRequestEvent.create(it, userProfile().latestContactList?.relays()?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!, message) } return null @@ -179,10 +179,10 @@ class Account( return createZapRequestFor(user.pubkeyHex) } - fun createZapRequestFor(userPubKeyHex: String): LnZapRequestEvent? { + fun createZapRequestFor(userPubKeyHex: String, message: String = ""): LnZapRequestEvent? { if (!isWriteable()) return null - return LnZapRequestEvent.create(userPubKeyHex, userProfile().latestContactList?.relays()?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!) + return LnZapRequestEvent.create(userPubKeyHex, userProfile().latestContactList?.relays()?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!, message) } fun report(note: Note, type: ReportEvent.ReportType, content: String = "") { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt index 8863b7af4..23dc9d6b6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt @@ -23,9 +23,10 @@ class LnZapRequestEvent( originalNote: EventInterface, relays: Set, privateKey: ByteArray, + message: String, createdAt: Long = Date().time / 1000 ): LnZapRequestEvent { - val content = "" + val content = message val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() var tags = listOf( listOf("e", originalNote.id()), @@ -45,9 +46,10 @@ class LnZapRequestEvent( userHex: String, relays: Set, privateKey: ByteArray, + message: String, createdAt: Long = Date().time / 1000 ): LnZapRequestEvent { - val content = "" + val content = message val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val tags = listOf( listOf("p", userHex), 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 8cf0b80c4..2fff33f83 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 @@ -130,7 +130,7 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC Button( modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp), onClick = { - val zapRequest = account.createZapRequestFor(toUserPubKeyHex) + val zapRequest = account.createZapRequestFor(toUserPubKeyHex, message) LightningAddressResolver().lnAddressInvoice( lud16, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index b9b6be39b..e2e72ae25 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -303,10 +303,11 @@ fun ZapReaction( val zapsState by baseNote.live().zaps.observeAsState() val zappedNote = zapsState?.note + val zapMessage = "" var wantsToZap by remember { mutableStateOf(false) } var wantsToChangeZapAmount by remember { mutableStateOf(false) } - + var wantsToSetZapOptions by remember { mutableStateOf(false) } val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) val context = LocalContext.current val scope = rememberCoroutineScope() @@ -347,7 +348,7 @@ fun ZapReaction( accountViewModel.zap( baseNote, account.zapAmountChoices.first() * 1000, - "", + zapMessage, context, onError = { scope.launch { @@ -370,6 +371,9 @@ fun ZapReaction( }, onLongClick = { wantsToChangeZapAmount = true + }, + onDoubleClick = { + wantsToSetZapOptions = true } ) ) { @@ -402,6 +406,10 @@ fun ZapReaction( UpdateZapAmountDialog({ wantsToChangeZapAmount = false }, account = account) } + if (wantsToSetZapOptions) { + ZapOptionsDialog({ wantsToSetZapOptions = false }, account = account, accountViewModel, baseNote) + } + if (zappedNote?.isZappedBy(account.userProfile()) == true) { zappingProgress = 1f Icon( @@ -530,7 +538,7 @@ fun ZapAmountChoicePopup( val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - + val zapMessage = "" val scope = rememberCoroutineScope() Popup( @@ -547,7 +555,7 @@ fun ZapAmountChoicePopup( accountViewModel.zap( baseNote, amountInSats * 1000, - "", + zapMessage, context, onError, onProgress @@ -571,7 +579,7 @@ fun ZapAmountChoicePopup( accountViewModel.zap( baseNote, amountInSats * 1000, - "", + zapMessage, context, onError, onProgress diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapOptionsDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapOptionsDialog.kt new file mode 100644 index 000000000..63af0de2f --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapOptionsDialog.kt @@ -0,0 +1,180 @@ +package com.vitorpamplona.amethyst.ui.note + +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CloseButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +class ZapOptionstViewModel : ViewModel() { + private var account: Account? = null + + var CustomAmount by mutableStateOf(TextFieldValue("")) + var CustomMessage by mutableStateOf(TextFieldValue("")) + + fun load(account: Account) { + this.account = account + } + + fun cancel() { + CustomAmount = TextFieldValue("") + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: AccountViewModel, baseNote: Note) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + val postViewModel: ZapOptionstViewModel = viewModel() + LaunchedEffect(account) { + postViewModel.load(account) + } + + Dialog( + onDismissRequest = { onClose() }, + properties = DialogProperties( + dismissOnClickOutside = false, + usePlatformDefaultWidth = false + ) + ) { + Surface() { + Column(modifier = Modifier.padding(10.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + CloseButton(onCancel = { + postViewModel.cancel() + onClose() + }) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 5.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + // stringResource(R.string.new_amount_in_sats + label = { Text(text = "Custom Amount") }, + value = postViewModel.CustomAmount, + onValueChange = { + postViewModel.CustomAmount = it + }, + keyboardOptions = KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Number + ), + placeholder = { + Text( + text = postViewModel.CustomAmount.text, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + }, + singleLine = true, + modifier = Modifier + .padding(end = 10.dp) + .weight(1f) + ) + } + Spacer(modifier = Modifier.height(10.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 5.dp), + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + // stringResource(R.string.new_amount_in_sats + label = { Text(text = "Message") }, + value = postViewModel.CustomMessage, + onValueChange = { + postViewModel.CustomMessage = it + }, + keyboardOptions = KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Text + ), + placeholder = { + Text( + text = postViewModel.CustomMessage.text, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + }, + singleLine = true, + modifier = Modifier + .padding(end = 10.dp) + .weight(1f) + ) + + Button( + onClick = { + scope.launch(Dispatchers.IO) { + accountViewModel.zap( + baseNote, + postViewModel.CustomAmount.text.toLong() * 1000, + postViewModel.CustomMessage.text, + context, + onError = { + scope.launch { + // zappingProgress = 0f + Toast + .makeText(context, it, Toast.LENGTH_SHORT) + .show() + } + }, + onProgress = { + scope.launch(Dispatchers.Main) { + // zappingProgress = it + } + } + ) + } + onClose() + } + ) { + Text(text = "Zap", color = Color.White) + } + } + } + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index aee875037..c7a820706 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -60,7 +60,7 @@ class AccountViewModel(private val account: Account) : ViewModel() { return } - val zapRequest = account.createZapRequestFor(note) + val zapRequest = account.createZapRequestFor(note, message) onProgress(0.10f) From ad70cfb8d7e6a15209a6828ff4189ffbc426779c Mon Sep 17 00:00:00 2001 From: Believethehype Date: Tue, 28 Mar 2023 22:22:28 +0200 Subject: [PATCH 02/27] updated ZapCustomDialog --- .../amethyst/ui/note/ReactionsRow.kt | 8 +- ...ZapOptionsDialog.kt => ZapCustomDialog.kt} | 80 +++++++++++-------- 2 files changed, 51 insertions(+), 37 deletions(-) rename app/src/main/java/com/vitorpamplona/amethyst/ui/note/{ZapOptionsDialog.kt => ZapCustomDialog.kt} (71%) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index e2e72ae25..991bdc668 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -307,7 +307,7 @@ fun ZapReaction( var wantsToZap by remember { mutableStateOf(false) } var wantsToChangeZapAmount by remember { mutableStateOf(false) } - var wantsToSetZapOptions by remember { mutableStateOf(false) } + var wantsToSetCustomZap by remember { mutableStateOf(false) } val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) val context = LocalContext.current val scope = rememberCoroutineScope() @@ -373,7 +373,7 @@ fun ZapReaction( wantsToChangeZapAmount = true }, onDoubleClick = { - wantsToSetZapOptions = true + wantsToSetCustomZap = true } ) ) { @@ -406,8 +406,8 @@ fun ZapReaction( UpdateZapAmountDialog({ wantsToChangeZapAmount = false }, account = account) } - if (wantsToSetZapOptions) { - ZapOptionsDialog({ wantsToSetZapOptions = false }, account = account, accountViewModel, baseNote) + if (wantsToSetCustomZap) { + ZapCustomDialog({ wantsToSetCustomZap = false }, account = account, accountViewModel, baseNote) } if (zappedNote?.isZappedBy(account.userProfile()) == true) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapOptionsDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt similarity index 71% rename from app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapOptionsDialog.kt rename to app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 63af0de2f..e367b1ba4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapOptionsDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -1,20 +1,9 @@ package com.vitorpamplona.amethyst.ui.note import android.widget.Toast -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.OutlinedTextField -import androidx.compose.material.Surface -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -25,10 +14,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.ViewModel @@ -39,24 +31,29 @@ import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch + class ZapOptionstViewModel : ViewModel() { private var account: Account? = null - var CustomAmount by mutableStateOf(TextFieldValue("")) - var CustomMessage by mutableStateOf(TextFieldValue("")) + var customAmount by mutableStateOf(TextFieldValue("1000")) + var customMessage by mutableStateOf(TextFieldValue("")) fun load(account: Account) { this.account = account } fun cancel() { - CustomAmount = TextFieldValue("") + customAmount = TextFieldValue("") } } +fun isNumeric(toCheck: String): Boolean { + return toCheck.toLongOrNull() != null +} + @OptIn(ExperimentalLayoutApi::class) @Composable -fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: AccountViewModel, baseNote: Note) { +fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: AccountViewModel, baseNote: Note) { val context = LocalContext.current val scope = rememberCoroutineScope() val postViewModel: ZapOptionstViewModel = viewModel() @@ -84,6 +81,26 @@ fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: Ac }) } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 5.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(com.vitorpamplona.amethyst.R.drawable.zap), + null, + modifier = Modifier.size(20.dp), + tint = Color.Unspecified + ) + Text( + text = "Custom Zap", + fontSize = 20.sp, + fontWeight = FontWeight.W500, + modifier = Modifier.padding(start = 10.dp) + ) + } Row( modifier = Modifier .fillMaxWidth() @@ -94,9 +111,11 @@ fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: Ac OutlinedTextField( // stringResource(R.string.new_amount_in_sats label = { Text(text = "Custom Amount") }, - value = postViewModel.CustomAmount, + value = postViewModel.customAmount, onValueChange = { - postViewModel.CustomAmount = it + if (isNumeric(it.text)) { + postViewModel.customAmount = it + } }, keyboardOptions = KeyboardOptions.Default.copy( capitalization = KeyboardCapitalization.None, @@ -104,7 +123,7 @@ fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: Ac ), placeholder = { Text( - text = postViewModel.CustomAmount.text, + text = postViewModel.customAmount.text, color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) }, @@ -125,9 +144,9 @@ fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: Ac OutlinedTextField( // stringResource(R.string.new_amount_in_sats label = { Text(text = "Message") }, - value = postViewModel.CustomMessage, + value = postViewModel.customMessage, onValueChange = { - postViewModel.CustomMessage = it + postViewModel.customMessage = it }, keyboardOptions = KeyboardOptions.Default.copy( capitalization = KeyboardCapitalization.None, @@ -135,7 +154,7 @@ fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: Ac ), placeholder = { Text( - text = postViewModel.CustomMessage.text, + text = postViewModel.customMessage.text, color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) }, @@ -150,20 +169,15 @@ fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: Ac scope.launch(Dispatchers.IO) { accountViewModel.zap( baseNote, - postViewModel.CustomAmount.text.toLong() * 1000, - postViewModel.CustomMessage.text, + postViewModel.customAmount.text.toLong() * 1000, + postViewModel.customMessage.text, context, onError = { - scope.launch { - // zappingProgress = 0f - Toast - .makeText(context, it, Toast.LENGTH_SHORT) - .show() - } + Toast + .makeText(context, it, Toast.LENGTH_SHORT).show() }, onProgress = { scope.launch(Dispatchers.Main) { - // zappingProgress = it } } ) @@ -171,7 +185,7 @@ fun ZapOptionsDialog(onClose: () -> Unit, account: Account, accountViewModel: Ac onClose() } ) { - Text(text = "Zap", color = Color.White) + Text(text = "⚡Zap ", color = Color.White) } } } From 42e0cda2e2fee43d3f658eddeba645fcef9a5114 Mon Sep 17 00:00:00 2001 From: Believethehype Date: Tue, 28 Mar 2023 22:24:00 +0200 Subject: [PATCH 03/27] don't reset dialog on close --- .../java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index e367b1ba4..09c9e8e63 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -43,7 +43,6 @@ class ZapOptionstViewModel : ViewModel() { } fun cancel() { - customAmount = TextFieldValue("") } } From e41da35a02713a7b9400bcb78a11d1f7c71f05b0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 28 Mar 2023 18:13:00 -0400 Subject: [PATCH 04/27] Adds image url when previews can't be loaded. --- .../ui/components/ZoomableImageView.kt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt index 84486200d..8910d83a6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt @@ -28,10 +28,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import coil.compose.AsyncImage +import coil.compose.AsyncImagePainter import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.PagerState import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.CloseButton +import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation import com.vitorpamplona.amethyst.ui.actions.SaveToGallery import net.engawapg.lib.zoomable.rememberZoomState import net.engawapg.lib.zoomable.zoomable @@ -46,6 +48,11 @@ fun ZoomableImageView(word: String, images: List = listOf(word)) { mutableStateOf(false) } + // store the dialog open or close state + var imageState by remember { + mutableStateOf(null) + } + if (imageExtension.matcher(word).matches()) { AsyncImage( model = word, @@ -63,8 +70,19 @@ fun ZoomableImageView(word: String, images: List = listOf(word)) { .combinedClickable( onClick = { dialogOpen = true }, onLongClick = { clipboardManager.setText(AnnotatedString(word)) } - ) + ), + onLoading = { + imageState = it + }, + onSuccess = { + imageState = it + } ) + + if (imageState !is AsyncImagePainter.State.Success) { + ClickableUrl(urlText = "$word ", url = word) + LoadingAnimation(indicatorSize = 15.dp) + } } else { VideoView(word) { dialogOpen = true } } @@ -86,7 +104,9 @@ fun ZoomableImageDialog(imageUrl: String, allImages: List = listOf(image var pagerState: PagerState = remember { PagerState() } Row( - modifier = Modifier.padding(10.dp).fillMaxWidth(), + modifier = Modifier + .padding(10.dp) + .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { From d61844436d07e12cbd577dcd781ad236198ab989 Mon Sep 17 00:00:00 2001 From: Believethehype Date: Wed, 29 Mar 2023 10:52:31 +0200 Subject: [PATCH 05/27] Update ProfileScreen.kt --- .../amethyst/ui/screen/loggedIn/ProfileScreen.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 82d515f7e..88159e4d5 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 @@ -430,12 +430,13 @@ private fun DrawAdditionalInfo(baseUser: User, account: Account, accountViewMode fontSize = 25.sp ) } - + } + Row(verticalAlignment = Alignment.CenterVertically) { user.bestUsername()?.let { Text( "@$it", color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp) + modifier = Modifier.padding(top = 1.dp, bottom = 1.dp) ) } } From d6a8b70852e824819bef493a9b6a8df99f6e8b7e Mon Sep 17 00:00:00 2001 From: Zoltan <34719275+ZsZolee@users.noreply.github.com> Date: Wed, 29 Mar 2023 10:57:23 +0200 Subject: [PATCH 06/27] Adding additional Hungarian Translation --- app/src/main/res/values-hu/strings.xml | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 6b7b6b0b0..3fd408b78 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -22,6 +22,7 @@ A felhasználó PubKulcs másolása A bejegyzés azonosítójának másolása Közvetít + Törlés Kérése Spam / Csalás bejelentés Megszemélyesítés bejelentés @@ -72,6 +73,7 @@ Nem sikerült feltölteni a képet Csomópont Címe Hozzászólások + Bájtok Hibák Hírfolyamod Privát Üzenetek listája @@ -119,6 +121,7 @@ Közvetlen üzenet küldése Szerkeszti a felhasználó metaadatait Követés + Visszakövetem Tiltás feloldása Felhasználói azonosító másolása Felhasználó feloldása @@ -172,6 +175,7 @@ Az összes ismert megjelölése olvasottként Az összes új megjelölése olvasottként Összes megjelölése olvasottként + Biztonsági Kulcsok ## Kulcs- és biztonsági mentési tippek \n\nFiókját titkos kulcs védi. A kulcs egy hosszú véletlenszerű karakterlánc, amely **nsec1**-el kezdődik. Bárki, aki az Ön titkos kulcsához hozzáfér, az Ön személyazonosságának használatával bármilyen tartalmat közzétehet. @@ -187,4 +191,78 @@ "Kitűző %1$s" Új Jelvényt kapott Kitüntetésben részesült + Bejegyzés vágólapra helyezve + A szerző @npub-ja vágólapra helyezve + A bejegyzés azonosítója (@note1) vágólapra helyezve + Szöveg Kijelölése + Github Gist w/ Proof + Telegram + Mastodon Post ID w/ Proof + Twitter Status w/ Proof + https://gist.github.com/<user>/<gist> + https://t.me/<proof post> + https://<server>/<user>/<proof post> + https://twitter.com/<user>/status/<proof post> + "<Nem sikerült a privát üzenetet visszafejteni>\n\nTéged egy privát/titkosított beszélgetésben %1$s és %2$s idéztek." + Új Fiók Hozzáadása + Fiókok + Fiók Kiválasztása + Új Fiók Hozzáadása + Aktív fiók + Tartalmaz privát kulcsot + Csak olvasásra, nincs privát kulcs + Vissza + Kiválaszt + Link megosztása + Megosztás + Szerző azonosító + Bejegyzés azonosító + Szöveg Másolása + Törlés + Kikövetés + Követem + Törlés Kérése + Az Amethyst kérni fogja, hogy töröljék a bejegyzésed azokról a csomópontokról, amelyekhez jelenleg csatlakozol. Nincs garancia arra, hogy a bejegyzésed ezekről a csomópontokról vagy más közvetítőkből (ahol tárolni lehet) véglegesen törlődik. + Tiltás + Törlés + Tiltás + Jelentés + Törlés + Ne mutasd újra + Spam vagy átverés + Trágárság vagy gyűlöletkeltő magatartás + Rosszindulatú megszemélyesítés + Meztelenség vagy pornográf tartalom + Illegális viselkedés + Az alkalmazásban egy felhasználó letiltása minden hozzá köthető tartalmat elrejt. Jegyzeteid továbbra is nyilvánosan megtekinthetők, beleértve a letiltott személyek számára is. A letiltott felhasználók a Biztonsági szűrők felületen jelennek meg. + + Visszaélés jelentése + Minden közzétett jelentés nyilvánosan látható lesz. + Opcionálisan a jelentéséhez további kontextust addhat… + További kontextus + Ok + Válassz egy okot… + Jelentés Elküldése + Tiltás és Jelentés + Tiltás + + Könyvjelzők + Privát Könyvjelzők + Publikus Könyvjelzők + Hozzáadás a Privát Könyvjelzőimhez + Hozzáadás a Publikus Könyvjelzőimhez + Törlés a Privát Könyvjelzőimből + Törlés a Publikus Könyvjelzőimből + + Wallet Connect Szolgáltatás + Engedélyezed a Nostr Secret-et, hogy az alkalmazás elhagyása nélkül Zap-ot küldjön. Tartsa a titkot biztonságban, és ha lehetséges, privát csomópontot használjon + Wallet Connect Publikus Kulcs + Wallet Connect Csomópont + Wallet Connect Titok + Titkos Kulcs Megjelenítése + nsec / hex privát kulcs + + Hozzájárulás összege sats-ban/string> + + "%1$s esemény keresése" From 3cd6bab7e901f34f88947e400f7a4e237d116670 Mon Sep 17 00:00:00 2001 From: Believethehype Date: Wed, 29 Mar 2023 18:26:44 +0200 Subject: [PATCH 07/27] Update UpdateZapAmountDialog.kt --- .../com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index ef6e46360..b4c0fd4af 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -370,7 +370,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) { onValueChange = { postViewModel.walletConnectRelay = it }, placeholder = { Text( - text = "relay.server.com", + text = "wss://relay.server.com", color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), maxLines = 1 ) From 19dab9ef4dc0a083314441e6109f7e3e1cbf8529 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 29 Mar 2023 15:57:03 -0400 Subject: [PATCH 08/27] Fixes hungarian xml --- app/src/main/res/values-hu/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 3fd408b78..5ca8b9da8 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -192,8 +192,8 @@ Új Jelvényt kapott Kitüntetésben részesült Bejegyzés vágólapra helyezve - A szerző @npub-ja vágólapra helyezve - A bejegyzés azonosítója (@note1) vágólapra helyezve + A szerző @npub-ja vágólapra helyezve + A bejegyzés azonosítója (@note1) vágólapra helyezve Szöveg Kijelölése Github Gist w/ Proof Telegram @@ -262,7 +262,7 @@ Titkos Kulcs Megjelenítése nsec / hex privát kulcs - Hozzájárulás összege sats-ban/string> + Hozzájárulás összege sats-ban "%1$s esemény keresése" From 0abac47ff2ad05b59f532675bd219d623cc272a6 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 29 Mar 2023 16:21:11 -0400 Subject: [PATCH 09/27] More forgiving Wallet Connect interface (nsec or hexes are accepted, relay url is trimmed and cleaned up, if relay doesn't have a wss://, adds one) --- .../vitorpamplona/amethyst/service/Nip47.kt | 3 +- .../amethyst/ui/note/UpdateZapAmountDialog.kt | 32 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip47.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip47.kt index 8b38638b3..4fb1bdcde 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip47.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip47.kt @@ -1,10 +1,11 @@ package com.vitorpamplona.amethyst.ui.note import android.net.Uri +import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.decodePublicKey import com.vitorpamplona.amethyst.model.toHexKey -data class Nip47URI(val pubKeyHex: String, val relayUri: String?, val secret: String?) +data class Nip47URI(val pubKeyHex: HexKey, val relayUri: String?, val secret: HexKey?) // Rename to the corect nip number when ready. object Nip47 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index b4c0fd4af..8315f59b5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -114,20 +114,38 @@ class UpdateZapAmountViewModel : ViewModel() { account?.changeZapAmounts(amountSet) if (walletConnectRelay.text.isNotBlank() && walletConnectPubkey.text.isNotBlank()) { + val pubkeyHex = try { + decodePublicKey(walletConnectPubkey.text.trim()).toHexKey() + } catch (e: Exception) { + null + } + + val relayUrl = walletConnectRelay.text.ifBlank { null }?.let { + var addedWSS = + if (!it.startsWith("wss://") && !it.startsWith("ws://")) "wss://$it" else it + if (addedWSS.endsWith("/")) addedWSS = addedWSS.dropLast(1) + + addedWSS + } + val unverifiedPrivKey = walletConnectSecret.text.ifBlank { null } - val privKey = try { + val privKeyHex = try { unverifiedPrivKey?.let { decodePublicKey(it).toHexKey() } } catch (e: Exception) { null } - account?.changeZapPaymentRequest( - Nip47URI( - walletConnectPubkey.text, - walletConnectRelay.text.ifBlank { null }, - privKey + if (pubkeyHex != null) { + account?.changeZapPaymentRequest( + Nip47URI( + pubkeyHex, + relayUrl, + privKeyHex + ) ) - ) + } else { + account?.changeZapPaymentRequest(null) + } } else { account?.changeZapPaymentRequest(null) } From ca79ceb1b90082920e8c3e8e0d1507d7fb570cad Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 29 Mar 2023 17:00:01 -0400 Subject: [PATCH 10/27] Adjusts custom zap messages with double click. --- .../amethyst/ui/note/ZapCustomDialog.kt | 120 +++++++++--------- app/src/main/res/values/strings.xml | 3 + 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 09c9e8e63..9cbb35b50 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.note import android.widget.Toast import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.runtime.Composable @@ -14,17 +15,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.actions.CloseButton @@ -42,20 +42,28 @@ class ZapOptionstViewModel : ViewModel() { this.account = account } + fun canSend(): Boolean { + return value() != null + } + + fun value(): Long? { + return try { + customAmount.text.trim().toLongOrNull() + } catch (e: Exception) { + null + } + } + fun cancel() { } } -fun isNumeric(toCheck: String): Boolean { - return toCheck.toLongOrNull() != null -} - -@OptIn(ExperimentalLayoutApi::class) @Composable fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: AccountViewModel, baseNote: Note) { val context = LocalContext.current val scope = rememberCoroutineScope() val postViewModel: ZapOptionstViewModel = viewModel() + LaunchedEffect(account) { postViewModel.load(account) } @@ -78,28 +86,32 @@ fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: Acc postViewModel.cancel() onClose() }) + + ZapButton( + isActive = postViewModel.canSend() + ) { + scope.launch(Dispatchers.IO) { + accountViewModel.zap( + baseNote, + postViewModel.value()!! * 1000L, + postViewModel.customMessage.text, + context, + onError = { + scope.launch { + Toast + .makeText(context, it, Toast.LENGTH_SHORT).show() + } + }, + onProgress = { + scope.launch(Dispatchers.Main) { + } + } + ) + } + onClose() + } } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 5.dp), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painter = painterResource(com.vitorpamplona.amethyst.R.drawable.zap), - null, - modifier = Modifier.size(20.dp), - tint = Color.Unspecified - ) - Text( - text = "Custom Zap", - fontSize = 20.sp, - fontWeight = FontWeight.W500, - modifier = Modifier.padding(start = 10.dp) - ) - } Row( modifier = Modifier .fillMaxWidth() @@ -109,12 +121,10 @@ fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: Acc ) { OutlinedTextField( // stringResource(R.string.new_amount_in_sats - label = { Text(text = "Custom Amount") }, + label = { Text(text = stringResource(id = R.string.amount_in_sats)) }, value = postViewModel.customAmount, onValueChange = { - if (isNumeric(it.text)) { - postViewModel.customAmount = it - } + postViewModel.customAmount = it }, keyboardOptions = KeyboardOptions.Default.copy( capitalization = KeyboardCapitalization.None, @@ -122,7 +132,7 @@ fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: Acc ), placeholder = { Text( - text = postViewModel.customAmount.text, + text = "100, 1000, 5000", color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) }, @@ -132,7 +142,7 @@ fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: Acc .weight(1f) ) } - Spacer(modifier = Modifier.height(10.dp)) + Spacer(modifier = Modifier.height(5.dp)) Row( modifier = Modifier @@ -142,7 +152,7 @@ fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: Acc ) { OutlinedTextField( // stringResource(R.string.new_amount_in_sats - label = { Text(text = "Message") }, + label = { Text(text = stringResource(id = R.string.custom_zaps_add_a_message)) }, value = postViewModel.customMessage, onValueChange = { postViewModel.customMessage = it @@ -153,7 +163,7 @@ fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: Acc ), placeholder = { Text( - text = postViewModel.customMessage.text, + text = stringResource(id = R.string.custom_zaps_add_a_message_example), color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) }, @@ -162,32 +172,22 @@ fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: Acc .padding(end = 10.dp) .weight(1f) ) - - Button( - onClick = { - scope.launch(Dispatchers.IO) { - accountViewModel.zap( - baseNote, - postViewModel.customAmount.text.toLong() * 1000, - postViewModel.customMessage.text, - context, - onError = { - Toast - .makeText(context, it, Toast.LENGTH_SHORT).show() - }, - onProgress = { - scope.launch(Dispatchers.Main) { - } - } - ) - } - onClose() - } - ) { - Text(text = "⚡Zap ", color = Color.White) - } } } } } } + +@Composable +fun ZapButton(isActive: Boolean, onPost: () -> Unit) { + Button( + onClick = { onPost() }, + shape = RoundedCornerShape(20.dp), + colors = ButtonDefaults + .buttonColors( + backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray + ) + ) { + Text(text = "⚡Zap ", color = Color.White) + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1a330edca..ded1ed4ae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -269,4 +269,7 @@ Pledge Amount in Sats "Looking for Event %1$s" + + Add a public message + Thank you for all your work! From a66f001033c3b7c6336957d88e4b60bf2c0cb4fa Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 29 Mar 2023 17:22:05 -0400 Subject: [PATCH 11/27] Adds Image Upload on Profile Edit pages. --- .../ui/actions/NewUserMetadataView.kt | 25 +++++++++ .../ui/actions/NewUserMetadataViewModel.kt | 56 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt index 3a683ec89..130c20f83 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.actions +import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -18,6 +19,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp @@ -30,9 +32,14 @@ import com.vitorpamplona.amethyst.model.Account @Composable fun NewUserMetadataView(onClose: () -> Unit, account: Account) { val postViewModel: NewUserMetadataViewModel = viewModel() + val context = LocalContext.current LaunchedEffect(Unit) { postViewModel.load(account) + + postViewModel.imageUploadingError.collect { error -> + Toast.makeText(context, error, Toast.LENGTH_SHORT).show() + } } Dialog( @@ -141,6 +148,15 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) { color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) }, + leadingIcon = { + UploadFromGallery( + isUploading = postViewModel.isUploadingImageForPicture, + tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + modifier = Modifier.padding(start = 5.dp) + ) { + postViewModel.uploadForPicture(it, context) + } + }, singleLine = true ) @@ -157,6 +173,15 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) { color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) }, + leadingIcon = { + UploadFromGallery( + isUploading = postViewModel.isUploadingImageForBanner, + tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + modifier = Modifier.padding(start = 5.dp) + ) { + postViewModel.uploadForBanner(it, context) + } + }, singleLine = true ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt index 0b8e272ff..e6a9bf2e0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt @@ -1,13 +1,20 @@ package com.vitorpamplona.amethyst.ui.actions +import android.content.Context +import android.net.Uri +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.model.GitHubIdentity import com.vitorpamplona.amethyst.service.model.MastodonIdentity import com.vitorpamplona.amethyst.service.model.TwitterIdentity +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch import java.io.ByteArrayInputStream import java.io.StringWriter @@ -30,6 +37,10 @@ class NewUserMetadataViewModel : ViewModel() { val github = mutableStateOf("") val mastodon = mutableStateOf("") + var isUploadingImageForPicture by mutableStateOf(false) + var isUploadingImageForBanner by mutableStateOf(false) + val imageUploadingError = MutableSharedFlow() + fun load(account: Account) { this.account = account @@ -127,4 +138,49 @@ class NewUserMetadataViewModel : ViewModel() { github.value = "" mastodon.value = "" } + + fun uploadForPicture(uri: Uri, context: Context) { + upload( + uri, + context, + onUploading = { + isUploadingImageForPicture = it + }, + onUploaded = { + picture.value = it + } + ) + } + + fun uploadForBanner(uri: Uri, context: Context) { + upload( + uri, + context, + onUploading = { + isUploadingImageForBanner = it + }, + onUploaded = { + banner.value = it + } + ) + } + + fun upload(it: Uri, context: Context, onUploading: (Boolean) -> Unit, onUploaded: (String) -> Unit) { + onUploading(true) + + ImageUploader.uploadImage( + uri = it, + contentResolver = context.contentResolver, + onSuccess = { imageUrl -> + onUploading(false) + onUploaded(imageUrl) + }, + onError = { + onUploading(false) + viewModelScope.launch { + imageUploadingError.emit("Failed to upload the image / video") + } + } + ) + } } From 8f14ccda86ff0a6fdc5f836ceeb23216d2fcc03b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 29 Mar 2023 18:06:19 -0400 Subject: [PATCH 12/27] BugFix Sticky notifications on account change --- .../vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt | 4 ++++ .../vitorpamplona/amethyst/ui/navigation/AppNavigation.kt | 4 ++++ .../vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt index 39dcc920e..72e7766a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt @@ -40,4 +40,8 @@ object NotificationFeedFilter : FeedFilter() { .toList() .reversed() } + + fun isDifferentAccount(account: Account): Boolean { + return this::account.isInitialized && this.account != account + } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index c536fb83c..a08f68c2e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -53,9 +53,13 @@ fun AppNavigation( GlobalFeedFilter.account = account val searchFeedViewModel: NostrGlobalFeedViewModel = viewModel() + val restartNotificationList = NotificationFeedFilter.isDifferentAccount(account) + NotificationFeedFilter.account = account val notifFeedViewModel: NotificationViewModel = viewModel() + if (restartNotificationList) notifFeedViewModel.clear() + NavHost(navController, startDestination = Route.Home.route) { Route.Search.let { route -> composable(route.route, route.arguments, content = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 34337677e..2e1956d1b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -160,7 +160,12 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { LocalCache.live.observeForever(cacheListener) } + fun clear() { + lastNotes = null + } + override fun onCleared() { + clear() LocalCache.live.removeObserver(cacheListener) super.onCleared() } From a8c8ee15399f7d81777de4f8247f536b03700908 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 29 Mar 2023 18:06:41 -0400 Subject: [PATCH 13/27] BugFix: Stick reply setup from Gigi. --- .../com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt | 3 +++ 1 file changed, 3 insertions(+) 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 881947e14..739ca540d 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 @@ -49,6 +49,9 @@ class NewPostViewModel : ViewModel() { this.mentions = currentMentions.plus(replyUser) } } + } ?: { + replyTos = null + mentions = null } quote?.let { From b079b3ef794e87b11ff11b09fa34eb3d7e3c430b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 29 Mar 2023 18:15:44 -0400 Subject: [PATCH 14/27] 0.30.2 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d324adec4..576e7ad1b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 108 - versionName "0.30.1" + versionCode 109 + versionName "0.30.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 2c8c4bede7fc17ecedbc8c7bef5640198595af68 Mon Sep 17 00:00:00 2001 From: Zoltan <34719275+ZsZolee@users.noreply.github.com> Date: Thu, 30 Mar 2023 10:36:40 +0200 Subject: [PATCH 15/27] Custom Zaps Hungarian Translation --- app/src/main/res/values-hu/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 5ca8b9da8..81296d243 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -265,4 +265,7 @@ Hozzájárulás összege sats-ban "%1$s esemény keresése" + + Nyilvános üzenet hozzáadása + Köszönöm a kemény munkát! From 7f305f498bcb1a3e8c53050d5e503af06a6414a6 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 09:10:27 -0400 Subject: [PATCH 16/27] Only display PoW status with 8 bits of zeros - 0x00.. --- .../main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 701b2f50b..f2012231c 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 @@ -350,7 +350,7 @@ fun NoteComposeInner( } val pow = noteEvent.getPoWRank() - if (pow > 1) { + if (pow > 8) { DisplayPoW(pow) } } From eff07bfb1e85b79d9d40f49e866eed24e8853bd0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 11:02:39 -0400 Subject: [PATCH 17/27] Breaks notification cards in chunks of 50 notifications each. --- .../amethyst/ui/screen/CardFeedViewModel.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 2e1956d1b..1b546d246 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -100,14 +100,21 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { } val allBaseNotes = zapsPerEvent.keys + boostsPerEvent.keys + reactionsPerEvent.keys - val multiCards = allBaseNotes.map { - MultiSetCard( - it, - boostsPerEvent.get(it) ?: emptyList(), - reactionsPerEvent.get(it) ?: emptyList(), - zapsPerEvent.get(it) ?: emptyMap() - ) - } + val multiCards = allBaseNotes.map { baseNote -> + val boostsInCard = boostsPerEvent[baseNote] ?: emptyList() + val reactionsInCard = reactionsPerEvent[baseNote] ?: emptyList() + val zapsInCard = zapsPerEvent[baseNote] ?: emptyMap() + + val singleList = (boostsInCard + zapsInCard.values + reactionsInCard).sortedBy { it.createdAt() } + singleList.chunked(50).map { chunk -> + MultiSetCard( + baseNote, + boostsInCard.filter { it in chunk }, + reactionsInCard.filter { it in chunk }, + zapsInCard.filter { it.value in chunk } + ) + } + }.flatten() val textNoteCards = notes.filter { it.event !is ReactionEvent && it.event !is RepostEvent && it.event !is LnZapEvent }.map { if (it.event is PrivateDmEvent) { From b4e409db4d7d92033a2c5a25fc4d65887e2a4dfc Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 13:58:28 -0400 Subject: [PATCH 18/27] Saves the position of the Notification feed --- .../amethyst/ui/navigation/AppNavigation.kt | 23 ++++++++--- .../amethyst/ui/navigation/Routes.kt | 3 +- .../amethyst/ui/screen/CardFeedView.kt | 36 +++++++++++++---- .../amethyst/ui/screen/CardFeedViewModel.kt | 7 +++- .../amethyst/ui/screen/LazyListState.kt | 1 + .../ui/screen/loggedIn/NotificationScreen.kt | 17 +++++++- app/src/main/res/drawable/alby.xml | 39 +++++++++++++++++++ 7 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 app/src/main/res/drawable/alby.xml diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index a08f68c2e..bba99be7a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -53,13 +53,9 @@ fun AppNavigation( GlobalFeedFilter.account = account val searchFeedViewModel: NostrGlobalFeedViewModel = viewModel() - val restartNotificationList = NotificationFeedFilter.isDifferentAccount(account) - NotificationFeedFilter.account = account val notifFeedViewModel: NotificationViewModel = viewModel() - if (restartNotificationList) notifFeedViewModel.clear() - NavHost(navController, startDestination = Route.Home.route) { Route.Search.let { route -> composable(route.route, route.arguments, content = { @@ -99,8 +95,25 @@ fun AppNavigation( }) } + Route.Notification.let { route -> + composable(route.route, route.arguments, content = { + val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false + + NotificationScreen( + notifFeedViewModel = notifFeedViewModel, + accountViewModel = accountViewModel, + navController = navController, + scrollToTop = scrollToTop + ) + + // Avoids running scroll to top when back button is pressed + if (scrollToTop) { + it.arguments?.remove("scrollToTop") + } + }) + } + composable(Route.Message.route, content = { ChatroomListScreen(accountViewModel, navController) }) - composable(Route.Notification.route, content = { NotificationScreen(notifFeedViewModel, accountViewModel, navController) }) composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, navController) }) composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, navController) }) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 99ea241af..66fd2fa05 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -37,8 +37,9 @@ sealed class Route( ) object Notification : Route( - route = "Notification", + route = "Notification?scrollToTop={scrollToTop}", icon = R.drawable.ic_notifications, + arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }), hasNewItems = { accountViewModel, cache -> notificationHasNewItems(accountViewModel, cache) } ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index 0744d4b1d..ed5e46c62 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -13,6 +13,7 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -33,7 +34,14 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @OptIn(ExperimentalMaterialApi::class) @Composable -fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String) { +fun CardFeedView( + viewModel: CardFeedViewModel, + accountViewModel: AccountViewModel, + navController: NavController, + routeForLastRead: String, + scrollStateKey: String? = null, + scrollToTop: Boolean = false +) { val feedState by viewModel.feedContent.collectAsState() var refreshing by remember { mutableStateOf(false) } @@ -57,10 +65,12 @@ fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewMode is CardFeedState.Loaded -> { refreshing = false FeedLoaded( - state, - accountViewModel, - navController, - routeForLastRead + state = state, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = routeForLastRead, + scrollStateKey = scrollStateKey, + scrollToTop = scrollToTop ) } CardFeedState.Loading -> { @@ -79,9 +89,21 @@ private fun FeedLoaded( state: CardFeedState.Loaded, accountViewModel: AccountViewModel, navController: NavController, - routeForLastRead: String + routeForLastRead: String, + scrollStateKey: String?, + scrollToTop: Boolean = false ) { - val listState = rememberLazyListState() + val listState = if (scrollStateKey != null) { + rememberForeverLazyListState(scrollStateKey) + } else { + rememberLazyListState() + } + + if (scrollToTop) { + LaunchedEffect(Unit) { + listState.scrollToItem(index = 0) + } + } LazyColumn( contentPadding = PaddingValues( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 1b546d246..9095dc023 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.screen import android.util.Log import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCacheState import com.vitorpamplona.amethyst.model.Note @@ -32,6 +33,7 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { private val _feedContent = MutableStateFlow(CardFeedState.Loading) val feedContent = _feedContent.asStateFlow() + private var lastAccount: Account? = null private var lastNotes: List? = null private fun refresh() { @@ -45,18 +47,21 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { private fun refreshSuspended() { val notes = dataSource.loadTop() - val lastNotesCopy = lastNotes + val thisAccount = (dataSource as? NotificationFeedFilter)?.account + val lastNotesCopy = if (thisAccount == lastAccount) lastNotes else null val oldNotesState = feedContent.value if (lastNotesCopy != null && oldNotesState is CardFeedState.Loaded) { val newCards = convertToCard(notes.minus(lastNotesCopy)) if (newCards.isNotEmpty()) { lastNotes = notes + lastAccount = (dataSource as? NotificationFeedFilter)?.account updateFeed((oldNotesState.feed.value + newCards).distinctBy { it.id() }.sortedBy { it.createdAt() }.reversed()) } } else { val cards = convertToCard(notes) lastNotes = notes + lastAccount = (dataSource as? NotificationFeedFilter)?.account updateFeed(cards) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LazyListState.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LazyListState.kt index 0a4a2d5cf..ebf122ee5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LazyListState.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LazyListState.kt @@ -11,6 +11,7 @@ private data class ScrollState(val index: Int, val scrollOffset: Int) object ScrollStateKeys { const val GLOBAL_SCREEN = "Global" + const val NOTIFICATION_SCREEN = "Notifications" val HOME_FOLLOWS = Route.Home.base + "Follows" val HOME_REPLIES = Route.Home.base + "FollowsReplies" } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 5411488c9..dfba51058 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -18,9 +18,15 @@ import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.screen.CardFeedView import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel +import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys @Composable -fun NotificationScreen(notifFeedViewModel: NotificationViewModel, accountViewModel: AccountViewModel, navController: NavController) { +fun NotificationScreen( + notifFeedViewModel: NotificationViewModel, + accountViewModel: AccountViewModel, + navController: NavController, + scrollToTop: Boolean = false +) { val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return @@ -48,7 +54,14 @@ fun NotificationScreen(notifFeedViewModel: NotificationViewModel, accountViewMod Column( modifier = Modifier.padding(vertical = 0.dp) ) { - CardFeedView(notifFeedViewModel, accountViewModel = accountViewModel, navController, Route.Notification.route) + CardFeedView( + viewModel = notifFeedViewModel, + accountViewModel = accountViewModel, + navController = navController, + routeForLastRead = Route.Notification.base, + scrollStateKey = ScrollStateKeys.NOTIFICATION_SCREEN, + scrollToTop = scrollToTop + ) } } } diff --git a/app/src/main/res/drawable/alby.xml b/app/src/main/res/drawable/alby.xml new file mode 100644 index 000000000..1188a62fc --- /dev/null +++ b/app/src/main/res/drawable/alby.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + From b4e39f2b73e8949156a50967725bbfac8b14fa78 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 16:10:13 -0400 Subject: [PATCH 19/27] Full Wallet Connect onboarding with Alby. --- app/src/main/AndroidManifest.xml | 7 ++ .../vitorpamplona/amethyst/ui/MainActivity.kt | 12 ++++ .../amethyst/ui/navigation/AppNavigation.kt | 21 ++++-- .../amethyst/ui/navigation/Routes.kt | 7 +- .../amethyst/ui/note/UpdateZapAmountDialog.kt | 48 ++++++++++--- .../amethyst/ui/screen/loggedIn/HomeScreen.kt | 13 +++- app/src/main/res/drawable/alby.xml | 67 ++++++++++++------- 7 files changed, 134 insertions(+), 41 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c22a472ac..518e6a539 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,6 +43,13 @@ + + + + + + + diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 3afecdcb7..49823b3e4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -21,6 +21,8 @@ import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.ServiceManager import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.service.relays.Client +import com.vitorpamplona.amethyst.ui.navigation.Route +import com.vitorpamplona.amethyst.ui.note.Nip47 import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.theme.AmethystTheme @@ -28,6 +30,8 @@ import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import java.net.URLEncoder +import java.nio.charset.StandardCharsets class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -40,6 +44,14 @@ class MainActivity : FragmentActivity() { Nip19.Type.EVENT -> "Event/${nip19.hex}" Nip19.Type.ADDRESS -> "Note/${nip19.hex}" else -> null + } ?: try { + intent?.data?.toString()?.let { + Nip47.parse(it) + val encodedUri = URLEncoder.encode(it, StandardCharsets.UTF_8.toString()) + Route.Home.base + "?nip47=" + encodedUri + } + } catch (e: Exception) { + null } Coil.setImageLoader { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index bba99be7a..d7418272c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -1,8 +1,12 @@ package com.vitorpamplona.amethyst.ui.navigation import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost @@ -39,6 +43,7 @@ fun AppNavigation( nextPage: String? = null ) { val homePagerState = rememberPagerState() + var actionableNextPage by remember { mutableStateOf(nextPage) } // Avoids creating ViewModels for performance reasons (up to 1 second delays) val accountState by accountViewModel.accountLiveData.observeAsState() @@ -76,8 +81,9 @@ fun AppNavigation( } Route.Home.let { route -> - composable(route.route, route.arguments, content = { + composable(route.route, route.arguments, content = { it -> val scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false + val nip47 = it.arguments?.getString("nip47") HomeScreen( homeFeedViewModel = homeFeedViewModel, @@ -85,13 +91,17 @@ fun AppNavigation( accountViewModel = accountViewModel, navController = navController, pagerState = homePagerState, - scrollToTop = scrollToTop + scrollToTop = scrollToTop, + nip47 = nip47 ) // Avoids running scroll to top when back button is pressed if (scrollToTop) { it.arguments?.remove("scrollToTop") } + if (nip47 != null) { + it.arguments?.remove("nip47") + } }) } @@ -178,7 +188,10 @@ fun AppNavigation( } } - if (nextPage != null) { - navController.navigate(nextPage) + actionableNextPage?.let { + LaunchedEffect(it) { + navController.navigate(it) + } + actionableNextPage = null } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 66fd2fa05..ddbe4d307 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -24,9 +24,12 @@ sealed class Route( get() = route.substringBefore("?") object Home : Route( - route = "Home?scrollToTop={scrollToTop}", + route = "Home?scrollToTop={scrollToTop}&nip47={nip47}", icon = R.drawable.ic_home, - arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }), + arguments = listOf( + navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }, + navArgument("nip47") { type = NavType.StringType; nullable = true; defaultValue = null } + ), hasNewItems = { accountViewModel, cache -> homeHasNewItems(accountViewModel, cache) } ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index 8315f59b5..7b0899c6b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction @@ -165,17 +166,40 @@ class UpdateZapAmountViewModel : ViewModel() { walletConnectSecret.text != (account?.zapPaymentRequest?.secret ?: "") ) } + + fun updateNIP47(uri: String) { + val contact = Nip47.parse(uri) + if (contact != null) { + walletConnectPubkey = + TextFieldValue(contact.pubKeyHex) + walletConnectRelay = + TextFieldValue(contact.relayUri ?: "") + walletConnectSecret = + TextFieldValue(contact.secret ?: "") + } + } } @OptIn(ExperimentalLayoutApi::class) @Composable -fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) { +fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account, nip47uri: String? = null) { val context = LocalContext.current val scope = rememberCoroutineScope() val postViewModel: UpdateZapAmountViewModel = viewModel() + val uri = LocalUriHandler.current LaunchedEffect(account) { postViewModel.load(account) + if (nip47uri != null) { + try { + postViewModel.updateNIP47(nip47uri) + } catch (e: IllegalArgumentException) { + scope.launch { + Toast.makeText(context, e.message, Toast.LENGTH_SHORT) + .show() + } + } + } } Dialog( @@ -299,6 +323,18 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) { stringResource(id = R.string.wallet_connect_service), Modifier.weight(1f) ) + + IconButton(onClick = { + runCatching { uri.openUri("https://nwc.getalby.com/apps/new?c=Amethyst") } + }) { + Icon( + painter = painterResource(R.drawable.alby), + null, + modifier = Modifier.size(24.dp), + tint = Color.Unspecified + ) + } + IconButton(onClick = { qrScanning = true }) { @@ -330,15 +366,7 @@ fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) { qrScanning = false if (!it.isNullOrEmpty()) { try { - val contact = Nip47.parse(it) - if (contact != null) { - postViewModel.walletConnectPubkey = - TextFieldValue(contact.pubKeyHex) - postViewModel.walletConnectRelay = - TextFieldValue(contact.relayUri ?: "") - postViewModel.walletConnectSecret = - TextFieldValue(contact.secret ?: "") - } + postViewModel.updateNIP47(it) } catch (e: IllegalArgumentException) { scope.launch { Toast.makeText(context, e.message, Toast.LENGTH_SHORT) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index 15676ba3c..c191de3aa 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -11,7 +11,11 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource @@ -28,6 +32,7 @@ import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.navigation.Route +import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog import com.vitorpamplona.amethyst.ui.screen.FeedView import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel @@ -42,10 +47,12 @@ fun HomeScreen( accountViewModel: AccountViewModel, navController: NavController, pagerState: PagerState, - scrollToTop: Boolean = false + scrollToTop: Boolean = false, + nip47: String? = null ) { val coroutineScope = rememberCoroutineScope() val account = accountViewModel.accountLiveData.value?.account ?: return + var wantsToAddNip47 by remember { mutableStateOf(nip47) } LaunchedEffect(accountViewModel) { HomeNewThreadFeedFilter.account = account @@ -55,6 +62,10 @@ fun HomeScreen( repliesFeedViewModel.invalidateData() } + if (wantsToAddNip47 != null) { + UpdateZapAmountDialog({ wantsToAddNip47 = null }, account = account, wantsToAddNip47) + } + val lifeCycleOwner = LocalLifecycleOwner.current DisposableEffect(accountViewModel) { val observer = LifecycleEventObserver { _, event -> diff --git a/app/src/main/res/drawable/alby.xml b/app/src/main/res/drawable/alby.xml index 1188a62fc..cb036c35e 100644 --- a/app/src/main/res/drawable/alby.xml +++ b/app/src/main/res/drawable/alby.xml @@ -1,39 +1,58 @@ + android:width="489dp" + android:height="489dp" + android:viewportWidth="489" + android:viewportHeight="489"> + android:pathData="M89.58,181.77C96.83,182.51 103.84,181.66 110.26,179.49L110.42,179.63L110.66,179.87C83.35,210.92 64.09,251.1 56.14,295.56C50.5,327.03 67.39,357.29 95.35,370.61C140.92,392.29 191.9,404.42 245.64,404.42C299.39,404.42 349.32,392.54 394.53,371.27C422.59,358.07 439.6,327.82 434.06,296.31C426.2,251.6 406.52,211.48 379.48,179.96C345.33,140.15 379.58,179.8 379.58,179.8C379.58,179.8 391.32,182.34 397.62,181.9C421.93,180.22 441.52,160.37 442.89,136.03C444.51,106.88 420.52,82.9 391.36,84.54C367.36,85.89 347.63,105.01 345.58,128.97C344.95,136.44 346.01,143.62 348.41,150.15L348.02,150.54L347.79,150.77C318.32,129.03 283.17,116.22 245.05,116.22C206.93,116.22 171.54,129.12 141.99,151.01L141.77,150.78L141.4,150.42L140.69,149.71C143.01,143.22 144.01,136.11 143.33,128.71C141.16,104.88 121.47,85.9 97.57,84.55C68.36,82.92 44.37,106.91 46.01,136.09C47.34,159.78 65.97,179.34 89.56,181.78L89.58,181.77Z" + android:strokeWidth="19.962" + android:fillColor="#ffffff" + android:strokeColor="#ffffff"/> - - - + + + + + + + + From a3e94dbaefba7dc2e543dd009dc58fadfd754f91 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 16:53:13 -0400 Subject: [PATCH 20/27] Dependency updates --- app/build.gradle | 10 +++++----- build.gradle | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 576e7ad1b..65d76a278 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -110,7 +110,7 @@ dependencies { implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05" // Bitcoin secp256k1 bindings to Android - implementation 'fr.acinq.secp256k1:secp256k1-kmp-jni-android:0.7.1' + implementation 'fr.acinq.secp256k1:secp256k1-kmp-jni-android:0.8.0' // Nostr Base Protocol implementation('com.github.vitorpamplona.NostrPostr:nostrpostrlib:master-SNAPSHOT') { @@ -133,14 +133,14 @@ dependencies { implementation 'androidx.security:security-crypto-ktx:1.1.0-alpha05' // view videos - implementation 'com.google.android.exoplayer:exoplayer:2.18.4' + implementation 'com.google.android.exoplayer:exoplayer:2.18.5' // Load images from the web. - implementation "io.coil-kt:coil-compose:2.2.2" + implementation "io.coil-kt:coil-compose:$coil_version" // view gifs - implementation "io.coil-kt:coil-gif:2.2.2" + implementation "io.coil-kt:coil-gif:$coil_version" // view svgs - implementation("io.coil-kt:coil-svg:2.2.2") + implementation "io.coil-kt:coil-svg:$coil_version" // Rendering clickable text implementation "com.google.accompanist:accompanist-flowlayout:$accompanist_version" diff --git a/build.gradle b/build.gradle index 26862ebcf..ee67b5b0c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,12 @@ buildscript { ext { - fragment_version = "1.5.5" - lifecycle_version = '2.6.0' - compose_ui_version = '1.4.0-rc01' + fragment_version = "1.5.6" + lifecycle_version = '2.6.1' + compose_ui_version = '1.5.0-alpha01' nav_version = "2.5.3" room_version = "2.4.3" - accompanist_version = "0.28.0" + accompanist_version = '0.30.0' + coil_version = '2.3.0' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { From 33467c62fa2c085ff0d9e64b55725068b33482bf Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 17:46:01 -0400 Subject: [PATCH 21/27] Adds PoW Display to the MasterNote of a Thread --- .../java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt | 2 +- .../com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) 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 f2012231c..50b561b2c 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 @@ -350,7 +350,7 @@ fun NoteComposeInner( } val pow = noteEvent.getPoWRank() - if (pow > 8) { + if (pow > 20) { DisplayPoW(pow) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index b5e05d6f9..813bdadf5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -58,6 +58,7 @@ import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer import com.vitorpamplona.amethyst.ui.note.BadgeDisplay import com.vitorpamplona.amethyst.ui.note.BlankNote import com.vitorpamplona.amethyst.ui.note.DisplayFollowingHashtagsInPost +import com.vitorpamplona.amethyst.ui.note.DisplayPoW import com.vitorpamplona.amethyst.ui.note.DisplayReward import com.vitorpamplona.amethyst.ui.note.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.note.HiddenNote @@ -283,6 +284,11 @@ fun NoteMaster( if (baseReward != null) { DisplayReward(baseReward, baseNote, account, navController) } + + val pow = noteEvent.getPoWRank() + if (pow > 20) { + DisplayPoW(pow) + } } } } From 04a3fe2f4c920d9f9a52ce648d33377da075f973 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 17:52:35 -0400 Subject: [PATCH 22/27] Groups Notifications on Double click in the navigation row. --- .../com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt | 1 + .../amethyst/ui/screen/loggedIn/NotificationScreen.kt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 9095dc023..65defcd94 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -173,6 +173,7 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { } fun clear() { + lastAccount = null lastNotes = null } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index dfba51058..96149588d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -30,6 +30,10 @@ fun NotificationScreen( val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return + if (scrollToTop) { + notifFeedViewModel.clear() + } + LaunchedEffect(accountViewModel) { NotificationFeedFilter.account = account notifFeedViewModel.invalidateData() From ef5980e90b6e664064d95d879d3983ec2a61b41f Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 18:16:14 -0400 Subject: [PATCH 23/27] BugFix: Some events not loading. --- .../amethyst/service/NostrSingleEventDataSource.kt | 4 +--- .../java/com/vitorpamplona/amethyst/service/relays/Relay.kt | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt index 3494b3aa0..59abaed3c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt @@ -82,8 +82,6 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") { return null } - val now = Date().time / 1000 - return reactionsToWatch.map { TypedFilter( types = FeedType.values().toSet(), @@ -151,7 +149,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") { val addresses = createAddressFilter() val addressReactions = createTagToAddressFilter() - singleEventChannel.typedFilters = listOfNotNull(reactions, missing, addresses, addressReactions).flatten().ifEmpty { null } + singleEventChannel.typedFilters = listOfNotNull(missing, addresses, reactions, addressReactions).flatten().ifEmpty { null } } fun add(eventId: Note) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index c4f819831..8ab0b4d97 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -182,7 +182,7 @@ class Relay( val filters = Client.getSubscriptionFilters(requestId).filter { activeTypes.intersect(it.types).isNotEmpty() } if (filters.isNotEmpty()) { val request = - """["REQ","$requestId",${filters.take(10).joinToString(",") { it.filter.toJson(url) }}]""" + """["REQ","$requestId",${filters.take(40).joinToString(",") { it.filter.toJson(url) }}]""" // println("FILTERSSENT $url $request") socket?.send(request) eventUploadCounterInBytes += request.bytesUsedInMemory() From 20068a40d4a5caaa639aa59603045d9a15f09e0b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 18:23:39 -0400 Subject: [PATCH 24/27] Activates more relays for chats. --- .../amethyst/service/relays/Constants.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt index 5632e9da5..a167a2d99 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt @@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.RelaySetupInfo object Constants { val activeTypes = setOf(FeedType.FOLLOWS, FeedType.PRIVATE_DMS) + val activeTypesChats = setOf(FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.PRIVATE_DMS) val activeTypesGlobalChats = setOf(FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.PRIVATE_DMS, FeedType.GLOBAL) val activeTypesSearch = setOf(FeedType.SEARCH) @@ -14,16 +15,18 @@ object Constants { } val defaultRelays = arrayOf( - // Free relays - RelaySetupInfo("wss://nostr.bitcoiner.social", read = true, write = true, feedTypes = activeTypes), - RelaySetupInfo("wss://relay.nostr.bg", read = true, write = true, feedTypes = activeTypes), + // Free relays for DMs and Follows + RelaySetupInfo("wss://no.str.cr", read = true, write = true, feedTypes = activeTypes), RelaySetupInfo("wss://relay.snort.social", read = true, write = true, feedTypes = activeTypes), RelaySetupInfo("wss://relay.damus.io", read = true, write = true, feedTypes = activeTypes), - RelaySetupInfo("wss://nostr.oxtr.dev", read = true, write = true, feedTypes = activeTypes), - RelaySetupInfo("wss://nostr-pub.wellorder.net", read = true, write = true, feedTypes = activeTypes), - RelaySetupInfo("wss://nostr.mom", read = true, write = true, feedTypes = activeTypes), - RelaySetupInfo("wss://no.str.cr", read = true, write = true, feedTypes = activeTypes), - RelaySetupInfo("wss://nos.lol", read = true, write = true, feedTypes = activeTypes), + + // Chats + RelaySetupInfo("wss://nostr.bitcoiner.social", read = true, write = true, feedTypes = activeTypesChats), + RelaySetupInfo("wss://relay.nostr.bg", read = true, write = true, feedTypes = activeTypesChats), + RelaySetupInfo("wss://nostr.oxtr.dev", read = true, write = true, feedTypes = activeTypesChats), + RelaySetupInfo("wss://nostr-pub.wellorder.net", read = true, write = true, feedTypes = activeTypesChats), + RelaySetupInfo("wss://nostr.mom", read = true, write = true, feedTypes = activeTypesChats), + RelaySetupInfo("wss://nos.lol", read = true, write = true, feedTypes = activeTypesChats), // Less Reliable // NewRelayListViewModel.Relay("wss://nostr.orangepill.dev", read = true, write = true, feedTypes = activeTypes), From 3137750a5858748fe3e34a153413a2f58aaf066b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 18:35:30 -0400 Subject: [PATCH 25/27] Centers the loading icon for images. --- .../ui/components/ZoomableImageView.kt | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt index 8910d83a6..729ecadfc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt @@ -10,8 +10,12 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.InlineTextContent +import androidx.compose.foundation.text.appendInlineContent +import androidx.compose.material.LocalTextStyle import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -24,7 +28,12 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.Placeholder +import androidx.compose.ui.text.PlaceholderVerticalAlign +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import coil.compose.AsyncImage @@ -81,7 +90,36 @@ fun ZoomableImageView(word: String, images: List = listOf(word)) { if (imageState !is AsyncImagePainter.State.Success) { ClickableUrl(urlText = "$word ", url = word) - LoadingAnimation(indicatorSize = 15.dp) + + val myId = "inlineContent" + val emptytext = buildAnnotatedString { + withStyle( + LocalTextStyle.current.copy(color = MaterialTheme.colors.primary).toSpanStyle() + ) { + append("") + appendInlineContent(myId, "[icon]") + } + } + val inlineContent = mapOf( + Pair( + myId, + InlineTextContent( + Placeholder( + width = 17.sp, + height = 17.sp, + placeholderVerticalAlign = PlaceholderVerticalAlign.Center + ) + ) { + LoadingAnimation() + } + ) + ) + + // Empty Text for Size of Icon + Text( + text = emptytext, + inlineContent = inlineContent + ) } } else { VideoView(word) { dialogOpen = true } From 3193a5e0ef329da13303ce42b0916cedbca6094b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 18:53:20 -0400 Subject: [PATCH 26/27] Updating search as new notes come in. --- .../NostrSearchEventOrUserDataSource.kt | 32 +++++++++---------- .../ui/screen/loggedIn/SearchScreen.kt | 17 +++++++++- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt index 3fae3330a..c481131cd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt @@ -35,25 +35,25 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") { null } - if (hexToWatch == null) { - return null - } - // downloads all the reactions to a given event. - return listOf( - TypedFilter( - types = FeedType.values().toSet(), - filter = JsonFilter( - ids = listOfNotNull(hexToWatch) + return listOfNotNull( + hexToWatch?.let { + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + ids = listOfNotNull(hexToWatch) + ) ) - ), - TypedFilter( - types = FeedType.values().toSet(), - filter = JsonFilter( - kinds = listOf(MetadataEvent.kind), - authors = listOfNotNull(hexToWatch) + }, + hexToWatch?.let { + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(MetadataEvent.kind), + authors = listOfNotNull(hexToWatch) + ) ) - ), + }, TypedFilter( types = FeedType.values().toSet(), filter = JsonFilter( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 319cd8383..5337064d8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -140,6 +140,9 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont val onlineSearch = NostrSearchEventOrUserDataSource + val dbState = LocalCache.live.observeAsState() + val db = dbState.value ?: return + val isTrailingIconVisible by remember { derivedStateOf { searchValue.isNotBlank() @@ -151,6 +154,18 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont CoroutineChannel(CoroutineChannel.CONFLATED) } + LaunchedEffect(db) { + if (searchValue.length > 1) { + withContext(Dispatchers.IO) { + searchResults.value = LocalCache.findUsersStartingWith(searchValue) + searchResultsNotes.value = + LocalCache.findNotesStartingWith(searchValue).sortedBy { it.createdAt() } + .reversed() + searchResultsChannels.value = LocalCache.findChannelsStartingWith(searchValue) + } + } + } + LaunchedEffect(Unit) { // Wait for text changes to stop for 300 ms before firing off search. withContext(Dispatchers.IO) { @@ -161,7 +176,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont .collectLatest { hashtagResults.value = findHashtags(it) - if (it.removePrefix("npub").removePrefix("note").length >= 4) { + if (it.length >= 4) { onlineSearch.search(it.trim()) } From 9fa3b1db34d83f9406575bd19d0e45700e91bf5b Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 30 Mar 2023 19:47:48 -0400 Subject: [PATCH 27/27] Keeping most items visible when grouping notification cards. --- .../com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 65defcd94..4e547522d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -110,7 +110,7 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { val reactionsInCard = reactionsPerEvent[baseNote] ?: emptyList() val zapsInCard = zapsPerEvent[baseNote] ?: emptyMap() - val singleList = (boostsInCard + zapsInCard.values + reactionsInCard).sortedBy { it.createdAt() } + val singleList = (boostsInCard + zapsInCard.values + reactionsInCard).sortedBy { it.createdAt() }.reversed() singleList.chunked(50).map { chunk -> MultiSetCard( baseNote,