From db3824c3c71dadb8d101ea2ba16e4bca50532de0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 6 Dec 2023 18:43:54 -0500 Subject: [PATCH] New Cashu Redeeming card. --- .../amethyst/ui/components/CashuRedeem.kt | 194 +++++++++++++++--- .../vitorpamplona/amethyst/ui/note/Icons.kt | 11 + .../ui/screen/loggedIn/AccountViewModel.kt | 30 +++ .../vitorpamplona/amethyst/ui/theme/Shape.kt | 1 + .../vitorpamplona/amethyst/ui/theme/Theme.kt | 8 +- app/src/main/res/drawable/cashu.xml | 5 - 6 files changed, 210 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt index 26f22f9aa..e68d1dcd3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt @@ -1,10 +1,12 @@ package com.vitorpamplona.amethyst.ui.components +import android.content.Context import android.content.Intent import android.net.Uri import androidx.compose.animation.Crossfade import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -12,7 +14,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card import androidx.compose.material3.Divider +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -22,7 +26,6 @@ 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.Alignment import androidx.compose.ui.Modifier @@ -35,21 +38,30 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDirection +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat.startActivity +import androidx.lifecycle.viewmodel.compose.viewModel +import com.fasterxml.jackson.databind.node.TextNode import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.ThemeType import com.vitorpamplona.amethyst.service.CashuProcessor import com.vitorpamplona.amethyst.service.CashuToken import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation import com.vitorpamplona.amethyst.ui.note.CashuIcon import com.vitorpamplona.amethyst.ui.note.CopyIcon +import com.vitorpamplona.amethyst.ui.note.OpenInNewIcon import com.vitorpamplona.amethyst.ui.note.ZapIcon +import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.AmethystTheme import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.QuoteBorder +import com.vitorpamplona.amethyst.ui.theme.Size18Modifier import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size20dp +import com.vitorpamplona.amethyst.ui.theme.SmallishBorder import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.subtleBorder import kotlinx.coroutines.Dispatchers @@ -84,13 +96,45 @@ fun CashuPreview(cashutoken: String, accountViewModel: AccountViewModel) { @Composable fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) { - val lud16 = remember(accountViewModel) { - accountViewModel.account.userProfile().info?.lud16 - } + CashuPreviewNew(token, accountViewModel::meltCashu, accountViewModel::toast) +} - val useWebService = false +@Composable +@Preview() +fun CashuPreviewPreview() { + val sharedPreferencesViewModel: SharedPreferencesViewModel = viewModel() + + sharedPreferencesViewModel.init() + sharedPreferencesViewModel.updateTheme(ThemeType.DARK) + + AmethystTheme(sharedPrefsViewModel = sharedPreferencesViewModel) { + Column() { + CashuPreview( + token = CashuToken("token", "mint", 32400, TextNode("")), + melt = { token, context, onDone -> + }, + toast = { title, message -> + } + ) + + CashuPreviewNew( + token = CashuToken("token", "mint", 32400, TextNode("")), + melt = { token, context, onDone -> + }, + toast = { title, message -> + } + ) + } + } +} + +@Composable +fun CashuPreview( + token: CashuToken, + melt: (CashuToken, Context, (String, String) -> Unit) -> Unit, + toast: (String, String) -> Unit +) { val context = LocalContext.current - val scope = rememberCoroutineScope() val clipboardManager = LocalClipboardManager.current Column( @@ -148,28 +192,10 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) { Button( onClick = { - if (lud16 != null) { - scope.launch(Dispatchers.IO) { - isRedeeming = true - CashuProcessor().melt( - token, - lud16, - onSuccess = { title, message -> - isRedeeming = false - accountViewModel.toast(title, message) - }, - onError = { title, message -> - isRedeeming = false - accountViewModel.toast(title, message) - }, - context - ) - } - } else { - accountViewModel.toast( - context.getString(R.string.no_lightning_address_set), - context.getString(R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats, accountViewModel.account.userProfile().toBestDisplayName()) - ) + isRedeeming = true + melt(token, context) { title, message -> + toast(title, message) + isRedeeming = false } }, shape = QuoteBorder, @@ -196,12 +222,12 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) { Button( onClick = { try { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("cashu://$token")) + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("cashu://${token.token}")) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK startActivity(context, intent, null) } catch (e: Exception) { - accountViewModel.toast("Cashu", context.getString(R.string.cashu_no_wallet_found)) + toast("Cashu", context.getString(R.string.cashu_no_wallet_found)) } }, shape = QuoteBorder, @@ -232,3 +258,111 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) { } } } + +@Composable +fun CashuPreviewNew( + token: CashuToken, + melt: (CashuToken, Context, (String, String) -> Unit) -> Unit, + toast: (String, String) -> Unit +) { + val context = LocalContext.current + val clipboardManager = LocalClipboardManager.current + + Card( + modifier = Modifier + .fillMaxWidth() + .padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp) + .clip(shape = QuoteBorder) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.cashu), + null, + modifier = Modifier.size(13.dp), + tint = Color.Unspecified + ) + + Text( + text = stringResource(R.string.cashu), + fontSize = 12.sp, + modifier = Modifier.padding(start = 5.dp, bottom = 1.dp) + ) + } + + Text( + text = "${token.totalAmount} ${stringResource(id = R.string.sats)}", + fontSize = 20.sp + ) + + Row(modifier = Modifier.padding(top = 5.dp)) { + var isRedeeming by remember { + mutableStateOf(false) + } + + FilledTonalButton( + onClick = { + isRedeeming = true + melt(token, context) { title, message -> + toast(title, message) + isRedeeming = false + } + }, + shape = SmallishBorder + ) { + if (isRedeeming) { + LoadingAnimation() + } else { + ZapIcon(Size20dp, tint = MaterialTheme.colorScheme.onBackground) + } + Spacer(StdHorzSpacer) + + Text( + "Redeem", + color = MaterialTheme.colorScheme.onBackground, + fontSize = 16.sp + ) + } + + Spacer(modifier = StdHorzSpacer) + + FilledTonalButton( + onClick = { + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("cashu://${token.token}")) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + + startActivity(context, intent, null) + } catch (e: Exception) { + toast("Cashu", context.getString(R.string.cashu_no_wallet_found)) + } + }, + shape = SmallishBorder, + contentPadding = PaddingValues(0.dp) + ) { + OpenInNewIcon(Size18Modifier, tint = MaterialTheme.colorScheme.onBackground) + } + + Spacer(modifier = StdHorzSpacer) + + FilledTonalButton( + onClick = { + // Copying the token to clipboard + clipboardManager.setText(AnnotatedString(token.token)) + }, + shape = SmallishBorder, + contentPadding = PaddingValues(0.dp) + ) { + CopyIcon(Size18Modifier, tint = MaterialTheme.colorScheme.onBackground) + } + } + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt index 0d49582cf..bb750a37c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt @@ -13,6 +13,7 @@ import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.Link import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material.icons.filled.PushPin import androidx.compose.material.icons.filled.Report import androidx.compose.material.icons.filled.VolumeOff @@ -196,6 +197,16 @@ fun CopyIcon(modifier: Modifier, tint: Color = Color.Unspecified) { ) } +@Composable +fun OpenInNewIcon(modifier: Modifier, tint: Color = Color.Unspecified) { + Icon( + imageVector = Icons.Default.OpenInNew, + stringResource(id = R.string.copy_to_clipboard), + tint = tint, + modifier = modifier + ) +} + @Composable fun ExpandLessIcon(modifier: Modifier) { Icon( 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 cd83ca014..a85b1e296 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 @@ -27,6 +27,8 @@ import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.model.UrlCachedPreviewer import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.UserState +import com.vitorpamplona.amethyst.service.CashuProcessor +import com.vitorpamplona.amethyst.service.CashuToken import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.Nip05Verifier import com.vitorpamplona.amethyst.service.Nip11CachedRetriever @@ -1047,6 +1049,34 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View account.dismissPaymentRequest(request) } } + + fun meltCashu( + token: CashuToken, + context: Context, + onDone: (String, String) -> Unit + ) { + val lud16 = account.userProfile().info?.lud16 + if (lud16 != null) { + viewModelScope.launch(Dispatchers.IO) { + CashuProcessor().melt( + token, + lud16, + onSuccess = { title, message -> + onDone(title, message) + }, + onError = { title, message -> + onDone(title, message) + }, + context + ) + } + } else { + onDone( + context.getString(R.string.no_lightning_address_set), + context.getString(R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats, account.userProfile().toBestDisplayName()) + ) + } + } } class HasNotificationDot(bottomNavigationItems: ImmutableList) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index f9a7ede87..d719ec38d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -27,6 +27,7 @@ val BottomTopHeight = Modifier.height(50.dp) val TabRowHeight = Modifier val SmallBorder = RoundedCornerShape(7.dp) +val SmallishBorder = RoundedCornerShape(9.dp) val QuoteBorder = RoundedCornerShape(15.dp) val ButtonBorder = RoundedCornerShape(20.dp) val EditFieldBorder = RoundedCornerShape(25.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index 48e0a95a5..bd07c08a5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -37,17 +37,17 @@ import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel private val DarkColorPalette = darkColorScheme( primary = Purple200, secondary = Teal200, - // secondary = Purple700, tertiary = Teal200, background = Color(0xFF000000), - surface = Color(0xFF000000) + surface = Color(0xFF000000), + surfaceVariant = Color(red = 29, green = 26, blue = 34) ) private val LightColorPalette = lightColorScheme( primary = Purple500, secondary = Teal200, - // secondary = Purple700, - tertiary = Teal200 + tertiary = Teal200, + surfaceVariant = Color(red = 250, green = 245, blue = 252) ) private val DarkNewItemBackground = DarkColorPalette.primary.copy(0.12f) diff --git a/app/src/main/res/drawable/cashu.xml b/app/src/main/res/drawable/cashu.xml index e08c5f079..46f884c4f 100644 --- a/app/src/main/res/drawable/cashu.xml +++ b/app/src/main/res/drawable/cashu.xml @@ -15,11 +15,6 @@ android:fillType="evenOdd" android:strokeWidth="1" android:pathData="M10.8,131.8 C10.392,132.291,10.42,132.4,10.951,132.4 C11.308,132.4,11.6,132.13,11.6,131.8 C11.6,131.47,11.532,131.2,11.449,131.2 C11.366,131.2,11.074,131.47,10.8,131.8 M58.237,133.448 C57.887,134.36,57.961,147.319,58.319,147.883 C58.512,148.186,59.963,148.41,62.213,148.483 L65.8,148.6 L65.917,152.179 C66.06,156.561,66.194,156.735,69.534,156.883 L72.2,157 L72.3,158.6 C72.666,164.449,72.746,164.553,77.084,164.8 L80.6,165 L80.8,167.6 C80.91,169.03,81.146,170.425,81.324,170.7 C81.746,171.353,133.611,171.436,134.826,170.786 C135.457,170.449,135.6,169.936,135.6,168.019 C135.6,164.388,135.587,164.4,139.394,164.4 C143.498,164.4,143.705,164.196,143.988,159.853 L144.2,156.6 L147.2,156.499 C152.502,156.32,152,157.187,152,148.206 C152,138.834,151.333,139.6,159.494,139.6 C167.001,139.6,166.4,138.869,166.4,148 C166.4,156.776,166.212,156.398,170.58,156.406 C174.674,156.414,174.678,156.418,174.858,160.045 C175.05,163.889,175.446,164.4,178.235,164.4 C180.806,164.4,181.2,164.9,181.2,168.16 C181.2,171.26,179.612,171.093,208.639,171.044 C238.254,170.994,236,171.283,236,167.541 C236,164.723,236.477,164.4,240.636,164.4 C243.695,164.4,244,163.988,244,159.854 C244,156.611,244.258,156.4,248.231,156.4 C252.25,156.4,252.332,156.315,252.483,151.98 L252.6,148.6 L255.965,148.295 C257.815,148.128,259.57,147.791,259.865,147.546 C260.608,146.929,260.64,134.288,259.9,133.703 C258.957,132.958,58.522,132.705,58.237,133.448 M81.117,143.379 C81.265,147.936,81.325,148,85.4,148 C89.475,148,89.535,147.936,89.683,143.379 L89.8,139.8 L92.895,139.683 C97.42,139.513,97.6,139.676,97.6,143.967 C97.6,147.999,97.586,147.984,101.42,147.995 C104.178,148.002,104.4,148.337,104.4,152.48 L104.4,156 L107.869,156 C112.781,156,113.783,157.444,112.379,162.5 L111.962,164 L108.405,164 C104.197,164,104.304,164.106,104.073,159.699 L103.898,156.368 L100.849,156.484 L97.8,156.6 L97.577,159.749 C97.301,163.639,96.925,164,93.147,164 C89.898,164,89.86,163.954,89.6,159.8 L89.4,156.6 L85.737,156.485 C81.274,156.346,81.265,156.337,81.117,151.779 L81,148.2 L77.6,147.991 C73.119,147.716,73.2,147.793,73.2,143.796 C73.2,139.702,73.348,139.57,77.8,139.704 L81,139.8 L81.117,143.379 M191.517,143.379 C191.659,147.734,191.848,148,194.8,148 C197.752,148,197.941,147.734,198.083,143.379 L198.2,139.8 L201.74,139.684 C206.333,139.533,206.4,139.595,206.4,144.006 C206.4,148.115,206.274,148,210.753,148 C214.691,148,214.8,148.12,214.8,152.48 L214.8,156 L217.181,156 C221.079,156,221.6,156.422,221.6,159.575 C221.6,163.374,221.062,164,217.801,164 C214.722,164,214.697,163.968,214.474,159.715 L214.3,156.4 L210.35,156.4 L206.4,156.4 L206.4,159.174 C206.4,163.812,206.238,164,202.234,164 C198.214,164,198.266,164.051,198,159.8 L197.8,156.6 L195.134,156.483 C191.794,156.335,191.66,156.161,191.517,151.779 L191.4,148.2 L187.821,148.083 C183.269,147.935,183.2,147.872,183.2,143.831 C183.2,139.617,183.281,139.544,187.794,139.686 L191.4,139.8 L191.517,143.379 M89.776,148.518 C89.66,148.82,89.618,150.582,89.683,152.433 L89.8,155.8 L93.515,155.915 L97.229,156.03 L97.115,152.115 L97,148.2 L93.493,148.084 C90.809,147.996,89.938,148.097,89.776,148.518 M198.17,148.533 C198.058,148.827,198.018,150.582,198.083,152.433 L198.2,155.8 L202.1,155.914 L206,156.029 L206,152.014 L206,148 L202.187,148 C199.379,148,198.321,148.14,198.17,148.533 M364.08,318.08 C363.816,318.344,363.6,318.698,363.6,318.867 C363.6,319.365,364.718,318.79,364.957,318.169 C365.223,317.475,364.735,317.425,364.08,318.08" /> -