New Cashu Redeeming card.

This commit is contained in:
Vitor Pamplona
2023-12-06 18:43:54 -05:00
parent d6de1d03d8
commit db3824c3c7
6 changed files with 210 additions and 39 deletions
@@ -1,10 +1,12 @@
package com.vitorpamplona.amethyst.ui.components package com.vitorpamplona.amethyst.ui.components
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth 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.foundation.layout.size
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.Divider import androidx.compose.material3.Divider
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -22,7 +26,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDirection 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat.startActivity 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.R
import com.vitorpamplona.amethyst.model.ThemeType
import com.vitorpamplona.amethyst.service.CashuProcessor import com.vitorpamplona.amethyst.service.CashuProcessor
import com.vitorpamplona.amethyst.service.CashuToken import com.vitorpamplona.amethyst.service.CashuToken
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
import com.vitorpamplona.amethyst.ui.note.CashuIcon import com.vitorpamplona.amethyst.ui.note.CashuIcon
import com.vitorpamplona.amethyst.ui.note.CopyIcon 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.note.ZapIcon
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel 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.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder 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.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.Size20dp 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.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.subtleBorder import com.vitorpamplona.amethyst.ui.theme.subtleBorder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -84,13 +96,45 @@ fun CashuPreview(cashutoken: String, accountViewModel: AccountViewModel) {
@Composable @Composable
fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) { fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
val lud16 = remember(accountViewModel) { CashuPreviewNew(token, accountViewModel::meltCashu, accountViewModel::toast)
accountViewModel.account.userProfile().info?.lud16 }
}
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 context = LocalContext.current
val scope = rememberCoroutineScope()
val clipboardManager = LocalClipboardManager.current val clipboardManager = LocalClipboardManager.current
Column( Column(
@@ -148,28 +192,10 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
Button( Button(
onClick = { onClick = {
if (lud16 != null) { isRedeeming = true
scope.launch(Dispatchers.IO) { melt(token, context) { title, message ->
isRedeeming = true toast(title, message)
CashuProcessor().melt( isRedeeming = false
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())
)
} }
}, },
shape = QuoteBorder, shape = QuoteBorder,
@@ -196,12 +222,12 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
Button( Button(
onClick = { onClick = {
try { 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 intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(context, intent, null) startActivity(context, intent, null)
} catch (e: Exception) { } 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, 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)
}
}
}
}
}
@@ -13,6 +13,7 @@ import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Link import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.MoreVert 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.PushPin
import androidx.compose.material.icons.filled.Report import androidx.compose.material.icons.filled.Report
import androidx.compose.material.icons.filled.VolumeOff 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 @Composable
fun ExpandLessIcon(modifier: Modifier) { fun ExpandLessIcon(modifier: Modifier) {
Icon( Icon(
@@ -27,6 +27,8 @@ import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.UserState 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.HttpClient
import com.vitorpamplona.amethyst.service.Nip05Verifier import com.vitorpamplona.amethyst.service.Nip05Verifier
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
@@ -1047,6 +1049,34 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
account.dismissPaymentRequest(request) 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<Route>) { class HasNotificationDot(bottomNavigationItems: ImmutableList<Route>) {
@@ -27,6 +27,7 @@ val BottomTopHeight = Modifier.height(50.dp)
val TabRowHeight = Modifier val TabRowHeight = Modifier
val SmallBorder = RoundedCornerShape(7.dp) val SmallBorder = RoundedCornerShape(7.dp)
val SmallishBorder = RoundedCornerShape(9.dp)
val QuoteBorder = RoundedCornerShape(15.dp) val QuoteBorder = RoundedCornerShape(15.dp)
val ButtonBorder = RoundedCornerShape(20.dp) val ButtonBorder = RoundedCornerShape(20.dp)
val EditFieldBorder = RoundedCornerShape(25.dp) val EditFieldBorder = RoundedCornerShape(25.dp)
@@ -37,17 +37,17 @@ import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
private val DarkColorPalette = darkColorScheme( private val DarkColorPalette = darkColorScheme(
primary = Purple200, primary = Purple200,
secondary = Teal200, secondary = Teal200,
// secondary = Purple700,
tertiary = Teal200, tertiary = Teal200,
background = Color(0xFF000000), background = Color(0xFF000000),
surface = Color(0xFF000000) surface = Color(0xFF000000),
surfaceVariant = Color(red = 29, green = 26, blue = 34)
) )
private val LightColorPalette = lightColorScheme( private val LightColorPalette = lightColorScheme(
primary = Purple500, primary = Purple500,
secondary = Teal200, secondary = Teal200,
// secondary = Purple700, tertiary = Teal200,
tertiary = Teal200 surfaceVariant = Color(red = 250, green = 245, blue = 252)
) )
private val DarkNewItemBackground = DarkColorPalette.primary.copy(0.12f) private val DarkNewItemBackground = DarkColorPalette.primary.copy(0.12f)
File diff suppressed because one or more lines are too long