From 7f19d5587c5c51694e5c1683cd1456f29f5dcb0c Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Fri, 20 Dec 2024 04:26:09 +0100 Subject: [PATCH 01/16] Implement sats/usd currency switcher on Wallet Dashboard --- .../dashboard/WalletDashboardContract.kt | 1 + .../wallet/dashboard/WalletDashboardScreen.kt | 13 ++- .../dashboard/WalletDashboardViewModel.kt | 17 ++++ .../android/wallet/dashboard/ui/AmountText.kt | 80 +++++++++++++++++++ .../wallet/dashboard/ui/WalletDashboard.kt | 14 +++- .../dashboard/ui/WalletDashboardLite.kt | 11 ++- .../wallet/utils/CurrencyConversionUtils.kt | 5 ++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/AmountText.kt diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardContract.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardContract.kt index 5b1e065d..b4dc07c9 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardContract.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardContract.kt @@ -19,6 +19,7 @@ interface WalletDashboardContract { val primalWallet: PrimalWallet? = null, val walletPreference: WalletPreference = WalletPreference.Undefined, val walletBalance: BigDecimal? = null, + val exchangeBtcUsdRate: Double? = null, val lastWalletUpdatedAt: Long? = null, val lowBalance: Boolean = false, val error: DashboardError? = null, diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt index d7196281..bf77c598 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt @@ -149,11 +149,11 @@ fun WalletDashboardScreen( val isScrolledToTop by remember(listState) { derivedStateOf { listState.firstVisibleItemScrollOffset == 0 } } val dashboardExpanded by rememberSaveable(isScrolledToTop) { mutableStateOf(isScrolledToTop) } - val dashboardLiteHeightDp = 80.dp var topBarHeight by remember { mutableIntStateOf(0) } var topBarFooterHeight by remember { mutableIntStateOf(0) } val density = LocalDensity.current + var currencyMode by remember { mutableStateOf(CurrencyMode.SATOSHI) } var shouldAddFooter by remember { mutableStateOf(false) } LaunchedEffect(pagingItems.itemCount, listState) { @@ -229,6 +229,9 @@ fun WalletDashboardScreen( WalletAction.Receive -> onReceiveClick() } }, + currencyMode = currencyMode, + onSwitchCurrencyMode = { currencyMode = it }, + exchangeBtcUsdRate = state.exchangeBtcUsdRate, ) false -> WalletDashboardLite( @@ -246,6 +249,9 @@ fun WalletDashboardScreen( WalletAction.Receive -> onReceiveClick() } }, + currencyMode = currencyMode, + onSwitchCurrencyMode = { currencyMode = it }, + exchangeBtcUsdRate = state.exchangeBtcUsdRate, ) } } @@ -349,4 +355,9 @@ fun WalletDashboardScreen( ) } +enum class CurrencyMode { + USD, + SATOSHI, +} + private const val DISABLED_WALLET_ALPHA = 0.42f diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt index 07fa052d..0e0c9e62 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt @@ -58,6 +58,7 @@ class WalletDashboardViewModel @Inject constructor( init { fetchWalletBalance() + fetchExchangeRate() subscribeToEvents() subscribeToActiveAccount() subscribeToPurchases() @@ -116,6 +117,22 @@ class WalletDashboardViewModel @Inject constructor( } } + private fun fetchExchangeRate() = + viewModelScope.launch { + try { + val btcRate = walletRepository.getExchangeRate( + userId = activeAccountStore.activeUserId(), + ) + setState { + copy( + exchangeBtcUsdRate = btcRate, + ) + } + } catch (error: WssException) { + Timber.e(error) + } + } + private fun enablePrimalWallet() = viewModelScope.launch { userRepository.updateWalletPreference( diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/AmountText.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/AmountText.kt new file mode 100644 index 00000000..70580b02 --- /dev/null +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/AmountText.kt @@ -0,0 +1,80 @@ +package net.primal.android.wallet.dashboard.ui + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import java.math.BigDecimal +import java.text.NumberFormat +import net.primal.android.R +import net.primal.android.theme.AppTheme +import net.primal.android.wallet.dashboard.CurrencyMode +import net.primal.android.wallet.utils.CurrencyConversionUtils.toSats +import net.primal.android.wallet.utils.CurrencyConversionUtils.toUsd + +@Composable +fun AmountText( + modifier: Modifier, + amount: BigDecimal?, + textSize: TextUnit = 42.sp, + amountColor: Color = Color.Unspecified, + currencyColor: Color = AppTheme.extraColorScheme.onSurfaceVariantAlt3, + currencyMode: CurrencyMode, + onSwitchCurrencyMode: (currencyMode: CurrencyMode) -> Unit, + exchangeBtcUsdRate: Double?, +) { + val numberFormat = remember { NumberFormat.getNumberInstance() } + + Row( + modifier = modifier + .clickable { + if (currencyMode == CurrencyMode.SATOSHI) { + onSwitchCurrencyMode(CurrencyMode.USD) + } else { + onSwitchCurrencyMode(CurrencyMode.SATOSHI) + } + } + .animateContentSize(), + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.Start, + ) { + Text( + text = if (currencyMode == CurrencyMode.SATOSHI) { + amount?.toSats()?.let { numberFormat.format(it.toLong()) } + } else { + amount?.toUsd(exchangeBtcUsdRate)?.let { numberFormat.format(it.toFloat()) } + } ?: "⌛", + textAlign = TextAlign.Center, + style = AppTheme.typography.displayMedium, + fontSize = textSize, + color = amountColor, + ) + + if (amount != null) { + Text( + modifier = Modifier.padding(bottom = (textSize.value / 6).dp), + text = if (currencyMode == CurrencyMode.SATOSHI) { + " ${stringResource(id = R.string.wallet_sats_suffix)}" + } else { + " ${stringResource(id = R.string.wallet_usd_suffix)}" + }, + textAlign = TextAlign.Center, + maxLines = 1, + style = AppTheme.typography.bodyMedium, + color = currencyColor, + ) + } + } +} diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt index e2e064ee..f624e295 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt @@ -13,28 +13,36 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal +import net.primal.android.wallet.dashboard.CurrencyMode @Composable fun WalletDashboard( modifier: Modifier, - enabled: Boolean = true, walletBalance: BigDecimal?, + exchangeBtcUsdRate: Double?, actions: List, onWalletAction: (WalletAction) -> Unit, + onSwitchCurrencyMode: (currencyMode: CurrencyMode) -> Unit, + enabled: Boolean = true, + currencyMode: CurrencyMode = CurrencyMode.SATOSHI, ) { val haptic = LocalHapticFeedback.current + Column( modifier = modifier, verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { - BtcAmountText( + AmountText( modifier = Modifier .wrapContentWidth() .padding(start = if (walletBalance != null) 32.dp else 0.dp) .padding(bottom = 32.dp), - amountInBtc = walletBalance ?: BigDecimal.ZERO, + amount = walletBalance ?: BigDecimal.ZERO, textSize = 48.sp, + currencyMode = currencyMode, + onSwitchCurrencyMode = onSwitchCurrencyMode, + exchangeBtcUsdRate = exchangeBtcUsdRate, ) WalletActionsRow( diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt index 2f0989bc..5ae70393 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal +import net.primal.android.wallet.dashboard.CurrencyMode @Composable fun WalletDashboardLite( @@ -16,19 +17,25 @@ fun WalletDashboardLite( walletBalance: BigDecimal?, actions: List, onWalletAction: (WalletAction) -> Unit, + currencyMode: CurrencyMode, + onSwitchCurrencyMode: (currencyMode: CurrencyMode) -> Unit, + exchangeBtcUsdRate: Double?, ) { Row( modifier = modifier, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Bottom, ) { - BtcAmountText( + AmountText( modifier = Modifier.graphicsLayer { clip = false translationY = 4.dp.toPx() }, - amountInBtc = walletBalance ?: BigDecimal.ZERO, + amount = walletBalance ?: BigDecimal.ZERO, textSize = 32.sp, + currencyMode = currencyMode, + onSwitchCurrencyMode = onSwitchCurrencyMode, + exchangeBtcUsdRate = exchangeBtcUsdRate, ) WalletActionsRow( diff --git a/app/src/main/kotlin/net/primal/android/wallet/utils/CurrencyConversionUtils.kt b/app/src/main/kotlin/net/primal/android/wallet/utils/CurrencyConversionUtils.kt index ffa2a018..9539496e 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/utils/CurrencyConversionUtils.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/utils/CurrencyConversionUtils.kt @@ -19,5 +19,10 @@ object CurrencyConversionUtils { fun BigDecimal.toSats(): ULong = multiply(BTC_IN_SATS.toBigDecimal()).toLong().toULong() + fun BigDecimal.toUsd(exchangeBtcUsdRate: Double?): BigDecimal { + val rate = exchangeBtcUsdRate ?: 0.0 + return multiply(BigDecimal(rate)) + } + fun Double.formatAsString() = String.format(Locale.US, "%.11f", this).trimEnd('0').trimEnd('.') } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8848f23b..f476b5e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -895,6 +895,7 @@ Warning sats + USD Received Sent Zap received From 268bc12d55f4a5addbad94a14a4c6413a4c2898c Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Fri, 20 Dec 2024 15:26:21 +0100 Subject: [PATCH 02/16] add animations and new usd suffix --- .../wallet/dashboard/WalletDashboardScreen.kt | 6 +-- .../ui/{AmountText.kt => FiatAmountText.kt} | 38 ++++++-------- .../wallet/dashboard/ui/WalletDashboard.kt | 50 ++++++++++++++----- .../dashboard/ui/WalletDashboardLite.kt | 50 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 5 files changed, 95 insertions(+), 50 deletions(-) rename app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/{AmountText.kt => FiatAmountText.kt} (63%) diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt index bf77c598..28ccbec2 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt @@ -153,7 +153,7 @@ fun WalletDashboardScreen( var topBarHeight by remember { mutableIntStateOf(0) } var topBarFooterHeight by remember { mutableIntStateOf(0) } val density = LocalDensity.current - var currencyMode by remember { mutableStateOf(CurrencyMode.SATOSHI) } + var currencyMode by remember { mutableStateOf(CurrencyMode.SATS) } var shouldAddFooter by remember { mutableStateOf(false) } LaunchedEffect(pagingItems.itemCount, listState) { @@ -356,8 +356,8 @@ fun WalletDashboardScreen( } enum class CurrencyMode { - USD, - SATOSHI, + FIAT, + SATS, } private const val DISABLED_WALLET_ALPHA = 0.42f diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/AmountText.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/FiatAmountText.kt similarity index 63% rename from app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/AmountText.kt rename to app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/FiatAmountText.kt index 70580b02..0e862eba 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/AmountText.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/FiatAmountText.kt @@ -1,7 +1,6 @@ package net.primal.android.wallet.dashboard.ui import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -20,42 +19,39 @@ import java.math.BigDecimal import java.text.NumberFormat import net.primal.android.R import net.primal.android.theme.AppTheme -import net.primal.android.wallet.dashboard.CurrencyMode -import net.primal.android.wallet.utils.CurrencyConversionUtils.toSats import net.primal.android.wallet.utils.CurrencyConversionUtils.toUsd @Composable -fun AmountText( +fun FiatAmountText( modifier: Modifier, amount: BigDecimal?, textSize: TextUnit = 42.sp, amountColor: Color = Color.Unspecified, currencyColor: Color = AppTheme.extraColorScheme.onSurfaceVariantAlt3, - currencyMode: CurrencyMode, - onSwitchCurrencyMode: (currencyMode: CurrencyMode) -> Unit, exchangeBtcUsdRate: Double?, ) { val numberFormat = remember { NumberFormat.getNumberInstance() } Row( modifier = modifier - .clickable { - if (currencyMode == CurrencyMode.SATOSHI) { - onSwitchCurrencyMode(CurrencyMode.USD) - } else { - onSwitchCurrencyMode(CurrencyMode.SATOSHI) - } - } .animateContentSize(), verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.Start, ) { + if (amount != null) { + Text( + modifier = Modifier.padding(bottom = (textSize.value / 2 - 5).dp), + text = "${stringResource(id = R.string.wallet_usd_prefix)} ", + textAlign = TextAlign.Center, + maxLines = 1, + style = AppTheme.typography.bodyMedium, + fontSize = textSize / 2, + color = currencyColor, + ) + } + Text( - text = if (currencyMode == CurrencyMode.SATOSHI) { - amount?.toSats()?.let { numberFormat.format(it.toLong()) } - } else { - amount?.toUsd(exchangeBtcUsdRate)?.let { numberFormat.format(it.toFloat()) } - } ?: "⌛", + text = amount?.toUsd(exchangeBtcUsdRate)?.let { numberFormat.format(it.toFloat()) } ?: "⌛", textAlign = TextAlign.Center, style = AppTheme.typography.displayMedium, fontSize = textSize, @@ -65,11 +61,7 @@ fun AmountText( if (amount != null) { Text( modifier = Modifier.padding(bottom = (textSize.value / 6).dp), - text = if (currencyMode == CurrencyMode.SATOSHI) { - " ${stringResource(id = R.string.wallet_sats_suffix)}" - } else { - " ${stringResource(id = R.string.wallet_usd_suffix)}" - }, + text = " ${stringResource(id = R.string.wallet_usd_suffix)}", textAlign = TextAlign.Center, maxLines = 1, style = AppTheme.typography.bodyMedium, diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt index f624e295..1d28fa2a 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt @@ -1,5 +1,12 @@ package net.primal.android.wallet.dashboard.ui +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -15,6 +22,7 @@ import androidx.compose.ui.unit.sp import java.math.BigDecimal import net.primal.android.wallet.dashboard.CurrencyMode +@OptIn(ExperimentalAnimationApi::class) @Composable fun WalletDashboard( modifier: Modifier, @@ -24,7 +32,7 @@ fun WalletDashboard( onWalletAction: (WalletAction) -> Unit, onSwitchCurrencyMode: (currencyMode: CurrencyMode) -> Unit, enabled: Boolean = true, - currencyMode: CurrencyMode = CurrencyMode.SATOSHI, + currencyMode: CurrencyMode = CurrencyMode.SATS, ) { val haptic = LocalHapticFeedback.current @@ -33,17 +41,35 @@ fun WalletDashboard( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { - AmountText( - modifier = Modifier - .wrapContentWidth() - .padding(start = if (walletBalance != null) 32.dp else 0.dp) - .padding(bottom = 32.dp), - amount = walletBalance ?: BigDecimal.ZERO, - textSize = 48.sp, - currencyMode = currencyMode, - onSwitchCurrencyMode = onSwitchCurrencyMode, - exchangeBtcUsdRate = exchangeBtcUsdRate, - ) + AnimatedContent( + modifier = modifier.fillMaxWidth(), + label = "Animated currency switch", + targetState = currencyMode, + transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, + ) { targetCurrencyMode -> + if (targetCurrencyMode == CurrencyMode.FIAT) { + FiatAmountText( + modifier = Modifier + .wrapContentWidth() + .padding(start = if (walletBalance != null) 32.dp else 0.dp) + .padding(bottom = 32.dp) + .clickable { onSwitchCurrencyMode(CurrencyMode.SATS) }, + amount = walletBalance ?: BigDecimal.ZERO, + textSize = 48.sp, + exchangeBtcUsdRate = exchangeBtcUsdRate, + ) + } else { + BtcAmountText( + modifier = Modifier + .wrapContentWidth() + .padding(start = if (walletBalance != null) 32.dp else 0.dp) + .padding(bottom = 32.dp) + .clickable { onSwitchCurrencyMode(CurrencyMode.FIAT) }, + amountInBtc = walletBalance ?: BigDecimal.ZERO, + textSize = 48.sp, + ) + } + } WalletActionsRow( modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt index 5ae70393..3c19e7a4 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt @@ -1,11 +1,19 @@ package net.primal.android.wallet.dashboard.ui +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal @@ -26,17 +34,35 @@ fun WalletDashboardLite( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Bottom, ) { - AmountText( - modifier = Modifier.graphicsLayer { - clip = false - translationY = 4.dp.toPx() - }, - amount = walletBalance ?: BigDecimal.ZERO, - textSize = 32.sp, - currencyMode = currencyMode, - onSwitchCurrencyMode = onSwitchCurrencyMode, - exchangeBtcUsdRate = exchangeBtcUsdRate, - ) + AnimatedContent( + modifier = modifier.fillMaxWidth(), + label = "Animated currency switch", + targetState = currencyMode, + transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, + ) { targetCurrencyMode -> + if (targetCurrencyMode == CurrencyMode.FIAT) { + FiatAmountText( + modifier = Modifier + .wrapContentWidth() + .padding(start = if (walletBalance != null) 32.dp else 0.dp) + .padding(bottom = 32.dp) + .clickable { onSwitchCurrencyMode(CurrencyMode.SATS) }, + amount = walletBalance ?: BigDecimal.ZERO, + textSize = 48.sp, + exchangeBtcUsdRate = exchangeBtcUsdRate, + ) + } else { + BtcAmountText( + modifier = Modifier + .wrapContentWidth() + .padding(start = if (walletBalance != null) 32.dp else 0.dp) + .padding(bottom = 32.dp) + .clickable { onSwitchCurrencyMode(CurrencyMode.FIAT) }, + amountInBtc = walletBalance ?: BigDecimal.ZERO, + textSize = 48.sp, + ) + } + } WalletActionsRow( modifier = Modifier, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f476b5e9..e4b963a5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -896,6 +896,7 @@ sats USD + $ Received Sent Zap received From 6703f2735a2fed81f1dac29398e8a9d63f3962a7 Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Sun, 22 Dec 2024 16:47:57 +0100 Subject: [PATCH 03/16] fix walletDashboardLite --- .../wallet/dashboard/WalletDashboardScreen.kt | 1 + .../dashboard/ui/WalletDashboardLite.kt | 21 +++++++++++-------- .../list/TransactionsLazyColumn.kt | 6 ++++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt index 28ccbec2..42ecda42 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt @@ -316,6 +316,7 @@ fun WalletDashboardScreen( .background(color = AppTheme.colorScheme.surfaceVariant) .padding(top = with(LocalDensity.current) { topBarHeight.toDp() }), pagingItems = pagingItems, + currencyMode = currencyMode, listState = listState, onProfileClick = onProfileClick, onTransactionClick = onTransactionClick, diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt index 3c19e7a4..f7aff3e5 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal @@ -35,7 +36,7 @@ fun WalletDashboardLite( verticalAlignment = Alignment.Bottom, ) { AnimatedContent( - modifier = modifier.fillMaxWidth(), + modifier = Modifier, label = "Animated currency switch", targetState = currencyMode, transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, @@ -43,23 +44,25 @@ fun WalletDashboardLite( if (targetCurrencyMode == CurrencyMode.FIAT) { FiatAmountText( modifier = Modifier - .wrapContentWidth() - .padding(start = if (walletBalance != null) 32.dp else 0.dp) - .padding(bottom = 32.dp) + .graphicsLayer { + clip = false + translationY = 4.dp.toPx() + } .clickable { onSwitchCurrencyMode(CurrencyMode.SATS) }, amount = walletBalance ?: BigDecimal.ZERO, - textSize = 48.sp, + textSize = 32.sp, exchangeBtcUsdRate = exchangeBtcUsdRate, ) } else { BtcAmountText( modifier = Modifier - .wrapContentWidth() - .padding(start = if (walletBalance != null) 32.dp else 0.dp) - .padding(bottom = 32.dp) + .graphicsLayer { + clip = false + translationY = 4.dp.toPx() + } .clickable { onSwitchCurrencyMode(CurrencyMode.FIAT) }, amountInBtc = walletBalance ?: BigDecimal.ZERO, - textSize = 48.sp, + textSize = 32.sp, ) } } diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt index f4882e62..59226a0b 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt @@ -24,6 +24,7 @@ import net.primal.android.R import net.primal.android.core.compose.heightAdjustableLoadingLazyListPlaceholder import net.primal.android.core.compose.isEmpty import net.primal.android.theme.AppTheme +import net.primal.android.wallet.dashboard.CurrencyMode @ExperimentalFoundationApi @Composable @@ -34,8 +35,9 @@ fun TransactionsLazyColumn( onProfileClick: (String) -> Unit, onTransactionClick: (String) -> Unit, paddingValues: PaddingValues = PaddingValues(0.dp), - header: (@Composable () -> Unit)? = null, - footer: (@Composable () -> Unit)? = null, + header: @Composable() (() -> Unit)? = null, + footer: @Composable() (() -> Unit)? = null, + currencyMode: CurrencyMode, ) { val today = stringResource(id = R.string.wallet_transactions_today).lowercase() val yesterday = stringResource(id = R.string.wallet_transactions_yesterday).lowercase() From c6bdab4ec83f18ff52c6c34d65e5ef41b3a89cda Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Sun, 22 Dec 2024 18:26:02 +0100 Subject: [PATCH 04/16] Implement support for USD currency for tsx list --- .../wallet/dashboard/WalletDashboardScreen.kt | 3 +- .../dashboard/ui/WalletDashboardLite.kt | 5 +- .../transactions/list/TransactionListItem.kt | 99 +++++++++++++------ .../list/TransactionsLazyColumn.kt | 11 ++- 4 files changed, 83 insertions(+), 35 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt index 42ecda42..85eeda92 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt @@ -153,7 +153,7 @@ fun WalletDashboardScreen( var topBarHeight by remember { mutableIntStateOf(0) } var topBarFooterHeight by remember { mutableIntStateOf(0) } val density = LocalDensity.current - var currencyMode by remember { mutableStateOf(CurrencyMode.SATS) } + var currencyMode by rememberSaveable { mutableStateOf(CurrencyMode.SATS) } var shouldAddFooter by remember { mutableStateOf(false) } LaunchedEffect(pagingItems.itemCount, listState) { @@ -317,6 +317,7 @@ fun WalletDashboardScreen( .padding(top = with(LocalDensity.current) { topBarHeight.toDp() }), pagingItems = pagingItems, currencyMode = currencyMode, + exchangeBtcUsdRate = state.exchangeBtcUsdRate, listState = listState, onProfileClick = onProfileClick, onTransactionClick = onTransactionClick, diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt index f7aff3e5..66b8cd2c 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt @@ -8,9 +8,6 @@ import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -59,7 +56,7 @@ fun WalletDashboardLite( .graphicsLayer { clip = false translationY = 4.dp.toPx() - } + } .clickable { onSwitchCurrencyMode(CurrencyMode.FIAT) }, amountInBtc = walletBalance ?: BigDecimal.ZERO, textSize = 32.sp, diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt index f53f16b7..324b705f 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt @@ -1,11 +1,16 @@ package net.primal.android.wallet.transactions.list +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.togetherWith import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -13,6 +18,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Schedule @@ -38,6 +44,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import java.math.BigDecimal import java.text.NumberFormat import java.time.Instant import java.time.ZoneId @@ -56,8 +63,11 @@ import net.primal.android.core.compose.icons.primaliconpack.WalletReceive import net.primal.android.core.compose.preview.PrimalPreview import net.primal.android.premium.legend.LegendaryCustomization import net.primal.android.theme.AppTheme +import net.primal.android.wallet.dashboard.CurrencyMode import net.primal.android.wallet.domain.TxState import net.primal.android.wallet.domain.TxType +import net.primal.android.wallet.utils.CurrencyConversionUtils.toBtc +import net.primal.android.wallet.utils.CurrencyConversionUtils.toUsd import net.primal.android.wallet.walletDepositColor import net.primal.android.wallet.walletTransactionIconBackgroundColor import net.primal.android.wallet.walletWithdrawColor @@ -68,6 +78,8 @@ fun TransactionListItem( numberFormat: NumberFormat, onAvatarClick: (String) -> Unit, onClick: (String) -> Unit, + currencyMode: CurrencyMode, + exchangeBtcUsdRate: Double?, ) { val alphaScale by if (data.txState == TxState.CREATED || data.txState == TxState.PROCESSING) { val infiniteTransition = rememberInfiniteTransition(label = "PendingPulsing${data.txId}") @@ -123,7 +135,10 @@ fun TransactionListItem( trailingContent = { TransactionTrailingContent( txType = data.txType, - txAmountInSats = numberFormat.format(data.txAmountInSats.toLong()), + txAmountInSats = BigDecimal.valueOf(data.txAmountInSats.toLong()), + currencyMode = currencyMode, + numberFormat = numberFormat, + exchangeBtcUsdRate = exchangeBtcUsdRate, ) }, ) @@ -236,33 +251,59 @@ private fun TransactionSupportContent( } @Composable -private fun TransactionTrailingContent(txAmountInSats: String, txType: TxType) { - Column( - horizontalAlignment = Alignment.End, - ) { - IconText( - text = txAmountInSats, - iconSize = 16.sp, - trailingIcon = when (txType) { - TxType.DEPOSIT -> PrimalIcons.WalletReceive - TxType.WITHDRAW -> PrimalIcons.WalletPay - }, - trailingIconTintColor = when (txType) { - TxType.DEPOSIT -> walletDepositColor - TxType.WITHDRAW -> walletWithdrawColor - }, - style = AppTheme.typography.bodyLarge, - fontWeight = FontWeight.Bold, - color = AppTheme.colorScheme.onSurface, - ) - IconText( - text = "${stringResource(id = R.string.wallet_sats_suffix)} ", - iconSize = 16.sp, - trailingIcon = PrimalIcons.WalletReceive, - trailingIconTintColor = Color.Transparent, - style = AppTheme.typography.bodySmall, - color = AppTheme.extraColorScheme.onSurfaceVariantAlt1, - ) +private fun TransactionTrailingContent( + txAmountInSats: BigDecimal, + txType: TxType, + currencyMode: CurrencyMode, + numberFormat: NumberFormat, + exchangeBtcUsdRate: Double?, +) { + AnimatedContent( + modifier = Modifier.width(100.dp), + label = "Animated currency switch", + targetState = currencyMode, + transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, + ) { targetCurrencyMode -> + Column( + horizontalAlignment = Alignment.End, + ) { + val text = if (targetCurrencyMode == CurrencyMode.FIAT) { + BigDecimal.valueOf(txAmountInSats.toBtc()).toUsd(exchangeBtcUsdRate) + .let { numberFormat.format(it.toFloat()) } + } else { + numberFormat.format(txAmountInSats.toLong()) + } + val suffix = if (targetCurrencyMode == CurrencyMode.FIAT) { + R.string.wallet_usd_suffix + } else { + R.string.wallet_sats_suffix + } + + IconText( + text = text, + iconSize = 16.sp, + trailingIcon = when (txType) { + TxType.DEPOSIT -> PrimalIcons.WalletReceive + TxType.WITHDRAW -> PrimalIcons.WalletPay + }, + trailingIconTintColor = when (txType) { + TxType.DEPOSIT -> walletDepositColor + TxType.WITHDRAW -> walletWithdrawColor + }, + style = AppTheme.typography.bodyLarge, + fontWeight = FontWeight.Bold, + color = AppTheme.colorScheme.onSurface, + ) + + IconText( + text = "${stringResource(id = suffix)} ", + iconSize = 16.sp, + trailingIcon = PrimalIcons.WalletReceive, + trailingIconTintColor = Color.Transparent, + style = AppTheme.typography.bodySmall, + color = AppTheme.extraColorScheme.onSurfaceVariantAlt1, + ) + } } } @@ -407,6 +448,8 @@ fun PreviewTransactionListItem(@PreviewParameter(provider = TxDataProvider::clas numberFormat = NumberFormat.getNumberInstance(), onAvatarClick = {}, onClick = {}, + currencyMode = CurrencyMode.SATS, + exchangeBtcUsdRate = 100000.0, ) } } diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt index 59226a0b..58e8a6bf 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt @@ -35,9 +35,14 @@ fun TransactionsLazyColumn( onProfileClick: (String) -> Unit, onTransactionClick: (String) -> Unit, paddingValues: PaddingValues = PaddingValues(0.dp), - header: @Composable() (() -> Unit)? = null, - footer: @Composable() (() -> Unit)? = null, + header: + @Composable() + (() -> Unit)? = null, + footer: + @Composable() + (() -> Unit)? = null, currencyMode: CurrencyMode, + exchangeBtcUsdRate: Double?, ) { val today = stringResource(id = R.string.wallet_transactions_today).lowercase() val yesterday = stringResource(id = R.string.wallet_transactions_yesterday).lowercase() @@ -83,6 +88,8 @@ fun TransactionsLazyColumn( numberFormat = numberFormat, onAvatarClick = onProfileClick, onClick = onTransactionClick, + currencyMode = currencyMode, + exchangeBtcUsdRate = exchangeBtcUsdRate, ) } } From 16059496bffd964bb804ac4a123173e6f680c5f2 Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Sun, 22 Dec 2024 19:11:07 +0100 Subject: [PATCH 05/16] Implement 'Currend USD value' row in tx details --- .../details/TransactionDetailsContract.kt | 1 + .../details/TransactionDetailsScreen.kt | 67 ++++++++++++++----- .../details/TransactionDetailsViewModel.kt | 19 ++++++ 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsContract.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsContract.kt index 55bd67d5..aa14dd93 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsContract.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsContract.kt @@ -9,5 +9,6 @@ interface TransactionDetailsContract { val txData: TransactionDetailDataUi? = null, val feedPost: FeedPostUi? = null, val articlePost: FeedArticleUi? = null, + val currentExchangeRate: Double? = null, ) } diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt index a89309b7..8bbfebf9 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt @@ -73,6 +73,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import java.math.BigDecimal import java.text.NumberFormat import java.time.Instant import java.time.format.FormatStyle @@ -108,6 +109,7 @@ import net.primal.android.wallet.domain.TxType import net.primal.android.wallet.transactions.details.TransactionDetailsContract.UiState import net.primal.android.wallet.transactions.list.TransactionIcon import net.primal.android.wallet.utils.CurrencyConversionUtils.toBtc +import net.primal.android.wallet.utils.CurrencyConversionUtils.toUsd import net.primal.android.wallet.walletDepositColor import net.primal.android.wallet.walletTransactionIconBackgroundColor import net.primal.android.wallet.walletWithdrawColor @@ -189,6 +191,7 @@ fun TransactionDetailsScreen( TransactionCard( txData = txData, onProfileClick = { profileId -> noteCallbacks.onProfileClick?.invoke(profileId) }, + currentExchangeRate = state.currentExchangeRate, ) } @@ -364,7 +367,11 @@ private fun TransactionDetailDataUi.typeToReadableString(useBitcoinTerm: Boolean } @Composable -private fun TransactionCard(txData: TransactionDetailDataUi, onProfileClick: (String) -> Unit) { +private fun TransactionCard( + txData: TransactionDetailDataUi, + onProfileClick: (String) -> Unit, + currentExchangeRate: Double?, +) { val isExpandable = txData.isZap && ( txData.txAmountInUsd != null || txData.exchangeRate != null || txData.totalFeeInSats != null || txData.invoice != null @@ -421,7 +428,7 @@ private fun TransactionCard(txData: TransactionDetailDataUi, onProfileClick: (St } if (expanded) { - TransactionExpandableDetails(txData = txData) + TransactionExpandableDetails(txData = txData, currentExchangeRate = currentExchangeRate) } if (isExpandable) { @@ -447,7 +454,7 @@ private fun TransactionCard(txData: TransactionDetailDataUi, onProfileClick: (St } @Composable -private fun TransactionExpandableDetails(txData: TransactionDetailDataUi) { +private fun TransactionExpandableDetails(txData: TransactionDetailDataUi, currentExchangeRate: Double?) { val numberFormat = remember { NumberFormat.getNumberInstance().apply { maximumFractionDigits = 2 } } Column { @@ -469,19 +476,13 @@ private fun TransactionExpandableDetails(txData: TransactionDetailDataUi) { value = txData.typeToReadableString(useBitcoinTerm = false), ) - if (txData.txAmountInUsd != null || txData.exchangeRate != null) { - val usdAmount = txData.txAmountInUsd ?: txData.exchangeRate?.let { rate -> - txData.txAmountInSats.toBtc() / rate.toDouble() - } - - numberFormat.formatSafely(usdAmount)?.let { formattedUsdAmount -> - PrimalDivider() - TransactionDetailListItem( - section = stringResource(id = R.string.wallet_transaction_details_original_usd_item), - value = "$$formattedUsdAmount", - ) - } - } + TransactionUsdValues( + currentExchangeRate = currentExchangeRate, + amountInSats = txData.txAmountInSats, + exchangeRate = txData.exchangeRate, + numberFormat = numberFormat, + amountInUsd = txData.txAmountInUsd, + ) txData.totalFeeInSats?.let { feeAmount -> PrimalDivider() @@ -534,6 +535,40 @@ private fun TransactionExpandableDetails(txData: TransactionDetailDataUi) { } } +@Composable +private fun TransactionUsdValues( + amountInUsd: Double?, + exchangeRate: String?, + amountInSats: ULong, + currentExchangeRate: Double?, + numberFormat: NumberFormat, +) { + if (amountInUsd != null || exchangeRate != null) { + val usdAmount = amountInUsd ?: exchangeRate?.let { rate -> + amountInSats.toBtc() / rate.toDouble() + } + + val currentUsdAmount = BigDecimal.valueOf(amountInSats.toBtc()) + .toUsd(currentExchangeRate) + + numberFormat.formatSafely(usdAmount)?.let { formattedUsdAmount -> + PrimalDivider() + TransactionDetailListItem( + section = stringResource(id = R.string.wallet_transaction_details_original_usd_item), + value = "$$formattedUsdAmount", + ) + } + + numberFormat.formatSafely(currentUsdAmount)?.let { formattedUsdAmount -> + PrimalDivider() + TransactionDetailListItem( + section = stringResource(id = R.string.wallet_transaction_details_currents_usd_item), + value = "$$formattedUsdAmount", + ) + } + } +} + private fun NumberFormat.formatSafely(any: Any?): String? { return try { format(any) diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt index c5cd8c50..a39503b5 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt @@ -22,6 +22,7 @@ import net.primal.android.networking.sockets.errors.WssException import net.primal.android.notes.feed.model.asFeedPostUi import net.primal.android.notes.repository.FeedRepository import net.primal.android.premium.legend.asLegendaryCustomization +import net.primal.android.user.accounts.active.ActiveAccountStore import net.primal.android.wallet.db.WalletTransaction import net.primal.android.wallet.repository.WalletRepository import net.primal.android.wallet.transactions.details.TransactionDetailsContract.UiState @@ -31,6 +32,7 @@ import timber.log.Timber @HiltViewModel class TransactionDetailsViewModel @Inject constructor( savedStateHandle: SavedStateHandle, + private val activeAccountStore: ActiveAccountStore, private val dispatcherProvider: CoroutineDispatcherProvider, private val walletRepository: WalletRepository, private val feedRepository: FeedRepository, @@ -46,6 +48,7 @@ class TransactionDetailsViewModel @Inject constructor( init { loadTransaction() + fetchExchangeRate() } private fun loadTransaction() = @@ -110,6 +113,22 @@ class TransactionDetailsViewModel @Inject constructor( } } + private fun fetchExchangeRate() = + viewModelScope.launch { + try { + val btcRate = walletRepository.getExchangeRate( + userId = activeAccountStore.activeUserId(), + ) + setState { + copy( + currentExchangeRate = btcRate, + ) + } + } catch (error: WssException) { + Timber.e(error) + } + } + private fun WalletTransaction.mapAsTransactionDataUi() = TransactionDetailDataUi( txId = this.data.id, From ea0ee45c36f8755a7e4f3585223d20a96db7bdf1 Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Mon, 23 Dec 2024 21:25:06 +0100 Subject: [PATCH 06/16] Resolve requested changes --- .../wallet/dashboard/ui/WalletDashboard.kt | 5 +- .../dashboard/ui/WalletDashboardLite.kt | 4 +- .../details/TransactionDetailsScreen.kt | 60 ++++++------ .../transactions/list/TransactionListItem.kt | 94 ++++++++++--------- .../list/TransactionsLazyColumn.kt | 8 +- .../wallet/utils/CurrencyConversionUtils.kt | 3 +- 6 files changed, 90 insertions(+), 84 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt index 1d28fa2a..5fc98a87 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.unit.sp import java.math.BigDecimal import net.primal.android.wallet.dashboard.CurrencyMode -@OptIn(ExperimentalAnimationApi::class) @Composable fun WalletDashboard( modifier: Modifier, @@ -43,11 +42,11 @@ fun WalletDashboard( ) { AnimatedContent( modifier = modifier.fillMaxWidth(), - label = "Animated currency switch", + label = "CurrencyContent", targetState = currencyMode, transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, ) { targetCurrencyMode -> - if (targetCurrencyMode == CurrencyMode.FIAT) { + if (targetCurrencyMode == CurrencyMode.FIAT && exchangeBtcUsdRate != null) { FiatAmountText( modifier = Modifier .wrapContentWidth() diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt index 66b8cd2c..7c5f9aaf 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt @@ -34,11 +34,11 @@ fun WalletDashboardLite( ) { AnimatedContent( modifier = Modifier, - label = "Animated currency switch", + label = "CurrencyContent", targetState = currencyMode, transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, ) { targetCurrencyMode -> - if (targetCurrencyMode == CurrencyMode.FIAT) { + if (targetCurrencyMode == CurrencyMode.FIAT && exchangeBtcUsdRate != null) { FiatAmountText( modifier = Modifier .graphicsLayer { diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt index 8bbfebf9..5fde4815 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -74,6 +75,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal +import java.math.RoundingMode import java.text.NumberFormat import java.time.Instant import java.time.format.FormatStyle @@ -476,13 +478,15 @@ private fun TransactionExpandableDetails(txData: TransactionDetailDataUi, curren value = txData.typeToReadableString(useBitcoinTerm = false), ) - TransactionUsdValues( - currentExchangeRate = currentExchangeRate, - amountInSats = txData.txAmountInSats, - exchangeRate = txData.exchangeRate, - numberFormat = numberFormat, - amountInUsd = txData.txAmountInUsd, - ) + if (txData.txAmountInUsd != null || txData.exchangeRate != null) { + TransactionUsdValues( + currentExchangeRate = currentExchangeRate, + amountInSats = txData.txAmountInSats, + originalExchangeRate = txData.exchangeRate, + numberFormat = numberFormat, + amountInUsd = txData.txAmountInUsd, + ) + } txData.totalFeeInSats?.let { feeAmount -> PrimalDivider() @@ -536,36 +540,34 @@ private fun TransactionExpandableDetails(txData: TransactionDetailDataUi, curren } @Composable -private fun TransactionUsdValues( +private fun ColumnScope.TransactionUsdValues( amountInUsd: Double?, - exchangeRate: String?, + originalExchangeRate: String?, amountInSats: ULong, currentExchangeRate: Double?, numberFormat: NumberFormat, ) { - if (amountInUsd != null || exchangeRate != null) { - val usdAmount = amountInUsd ?: exchangeRate?.let { rate -> - amountInSats.toBtc() / rate.toDouble() - } + val usdAmount = amountInUsd ?: originalExchangeRate?.let { rate -> + amountInSats.toBtc() / rate.toDouble() + } - val currentUsdAmount = BigDecimal.valueOf(amountInSats.toBtc()) - .toUsd(currentExchangeRate) + val currentUsdAmount = BigDecimal.valueOf(amountInSats.toBtc()) + .toUsd(currentExchangeRate) - numberFormat.formatSafely(usdAmount)?.let { formattedUsdAmount -> - PrimalDivider() - TransactionDetailListItem( - section = stringResource(id = R.string.wallet_transaction_details_original_usd_item), - value = "$$formattedUsdAmount", - ) - } + numberFormat.formatSafely(usdAmount)?.let { formattedUsdAmount -> + PrimalDivider() + TransactionDetailListItem( + section = stringResource(id = R.string.wallet_transaction_details_original_usd_item), + value = "$$formattedUsdAmount", + ) + } - numberFormat.formatSafely(currentUsdAmount)?.let { formattedUsdAmount -> - PrimalDivider() - TransactionDetailListItem( - section = stringResource(id = R.string.wallet_transaction_details_currents_usd_item), - value = "$$formattedUsdAmount", - ) - } + numberFormat.formatSafely(currentUsdAmount)?.let { formattedUsdAmount -> + PrimalDivider() + TransactionDetailListItem( + section = stringResource(id = R.string.wallet_transaction_details_currents_usd_item), + value = "$$formattedUsdAmount", + ) } } diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt index 324b705f..25ed4c6a 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt @@ -14,14 +14,18 @@ import androidx.compose.animation.togetherWith import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Schedule +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.Surface @@ -258,52 +262,56 @@ private fun TransactionTrailingContent( numberFormat: NumberFormat, exchangeBtcUsdRate: Double?, ) { - AnimatedContent( - modifier = Modifier.width(100.dp), - label = "Animated currency switch", - targetState = currencyMode, - transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, - ) { targetCurrencyMode -> - Column( - horizontalAlignment = Alignment.End, - ) { - val text = if (targetCurrencyMode == CurrencyMode.FIAT) { - BigDecimal.valueOf(txAmountInSats.toBtc()).toUsd(exchangeBtcUsdRate) - .let { numberFormat.format(it.toFloat()) } - } else { - numberFormat.format(txAmountInSats.toLong()) - } - val suffix = if (targetCurrencyMode == CurrencyMode.FIAT) { - R.string.wallet_usd_suffix - } else { - R.string.wallet_sats_suffix - } + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - IconText( - text = text, - iconSize = 16.sp, - trailingIcon = when (txType) { - TxType.DEPOSIT -> PrimalIcons.WalletReceive - TxType.WITHDRAW -> PrimalIcons.WalletPay - }, - trailingIconTintColor = when (txType) { - TxType.DEPOSIT -> walletDepositColor - TxType.WITHDRAW -> walletWithdrawColor - }, - style = AppTheme.typography.bodyLarge, - fontWeight = FontWeight.Bold, - color = AppTheme.colorScheme.onSurface, - ) + AnimatedContent( + modifier = Modifier.width(50.dp), + label = "CurrencyContent", + targetState = currencyMode, + transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, + ) { targetCurrencyMode -> + Column( + horizontalAlignment = Alignment.End, + ) { + val text = if (targetCurrencyMode == CurrencyMode.FIAT) { + BigDecimal.valueOf(txAmountInSats.toBtc()).toUsd(exchangeBtcUsdRate) + .let { numberFormat.format(it.toFloat()) } + } else { + numberFormat.format(txAmountInSats.toLong()) + } + val suffix = if (targetCurrencyMode == CurrencyMode.FIAT) { + R.string.wallet_usd_suffix + } else { + R.string.wallet_sats_suffix + } - IconText( - text = "${stringResource(id = suffix)} ", - iconSize = 16.sp, - trailingIcon = PrimalIcons.WalletReceive, - trailingIconTintColor = Color.Transparent, - style = AppTheme.typography.bodySmall, - color = AppTheme.extraColorScheme.onSurfaceVariantAlt1, - ) + Text( + text = text, + style = AppTheme.typography.bodyLarge, + fontWeight = FontWeight.Bold, + color = AppTheme.colorScheme.onSurface, + ) + + Text( + text = stringResource(id = suffix), + style = AppTheme.typography.bodySmall, + color = AppTheme.extraColorScheme.onSurfaceVariantAlt1, + ) + } } + + Icon( + modifier = Modifier.size(16.dp), + imageVector = when (txType) { + TxType.DEPOSIT -> PrimalIcons.WalletReceive + TxType.WITHDRAW -> PrimalIcons.WalletPay + }, + contentDescription = null, + tint = when (txType) { + TxType.DEPOSIT -> walletDepositColor + TxType.WITHDRAW -> walletWithdrawColor + } + ) } } diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt index 58e8a6bf..e9b57990 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionsLazyColumn.kt @@ -35,12 +35,8 @@ fun TransactionsLazyColumn( onProfileClick: (String) -> Unit, onTransactionClick: (String) -> Unit, paddingValues: PaddingValues = PaddingValues(0.dp), - header: - @Composable() - (() -> Unit)? = null, - footer: - @Composable() - (() -> Unit)? = null, + header: (@Composable () -> Unit)? = null, + footer: (@Composable () -> Unit)? = null, currencyMode: CurrencyMode, exchangeBtcUsdRate: Double?, ) { diff --git a/app/src/main/kotlin/net/primal/android/wallet/utils/CurrencyConversionUtils.kt b/app/src/main/kotlin/net/primal/android/wallet/utils/CurrencyConversionUtils.kt index 9539496e..f3995572 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/utils/CurrencyConversionUtils.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/utils/CurrencyConversionUtils.kt @@ -1,6 +1,7 @@ package net.primal.android.wallet.utils import java.math.BigDecimal +import java.math.RoundingMode import java.util.* @SuppressWarnings("ImplicitDefaultLocale") @@ -21,7 +22,7 @@ object CurrencyConversionUtils { fun BigDecimal.toUsd(exchangeBtcUsdRate: Double?): BigDecimal { val rate = exchangeBtcUsdRate ?: 0.0 - return multiply(BigDecimal(rate)) + return multiply(BigDecimal(rate)).setScale(2, RoundingMode.HALF_EVEN) } fun Double.formatAsString() = String.format(Locale.US, "%.11f", this).trimEnd('0').trimEnd('.') From ea0baf7a15a560dd46b994e52a4186fa81511ae0 Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Mon, 23 Dec 2024 21:27:29 +0100 Subject: [PATCH 07/16] Format composables --- .../primal/android/wallet/dashboard/ui/WalletDashboard.kt | 1 - .../transactions/details/TransactionDetailsScreen.kt | 1 - .../wallet/transactions/list/TransactionListItem.kt | 7 +------ 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt index 5fc98a87..253ce797 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt @@ -1,7 +1,6 @@ package net.primal.android.wallet.dashboard.ui import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt index 5fde4815..2888e72f 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt @@ -75,7 +75,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal -import java.math.RoundingMode import java.text.NumberFormat import java.time.Instant import java.time.format.FormatStyle diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt index 25ed4c6a..33c603fc 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Schedule -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults @@ -38,7 +37,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -47,7 +45,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import java.math.BigDecimal import java.text.NumberFormat import java.time.Instant @@ -56,7 +53,6 @@ import java.time.format.DateTimeFormatter import kotlin.time.Duration.Companion.seconds import net.primal.android.R import net.primal.android.attachments.domain.CdnImage -import net.primal.android.core.compose.IconText import net.primal.android.core.compose.UniversalAvatarThumbnail import net.primal.android.core.compose.WrappedContentWithSuffix import net.primal.android.core.compose.icons.PrimalIcons @@ -263,7 +259,6 @@ private fun TransactionTrailingContent( exchangeBtcUsdRate: Double?, ) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - AnimatedContent( modifier = Modifier.width(50.dp), label = "CurrencyContent", @@ -310,7 +305,7 @@ private fun TransactionTrailingContent( tint = when (txType) { TxType.DEPOSIT -> walletDepositColor TxType.WITHDRAW -> walletWithdrawColor - } + }, ) } } From 12dd70ad44124a6db47b88036a8bd1c465f5f1b2 Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Mon, 23 Dec 2024 22:02:17 +0100 Subject: [PATCH 08/16] Modify max and min width for tsxItem --- .../android/wallet/transactions/list/TransactionListItem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt index 33c603fc..c0cea347 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt @@ -20,7 +20,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Schedule @@ -260,7 +260,7 @@ private fun TransactionTrailingContent( ) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { AnimatedContent( - modifier = Modifier.width(50.dp), + modifier = Modifier.widthIn(min = 45.dp, max = 85.dp), label = "CurrencyContent", targetState = currencyMode, transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, From 9f5e463abb084264166f674227101da6d1959e6b Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Mon, 23 Dec 2024 23:29:12 +0100 Subject: [PATCH 09/16] Change animation for tsx item --- .../android/wallet/transactions/list/TransactionListItem.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt index c0cea347..e6d70fcc 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt @@ -260,10 +260,9 @@ private fun TransactionTrailingContent( ) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { AnimatedContent( - modifier = Modifier.widthIn(min = 45.dp, max = 85.dp), + modifier = Modifier.widthIn(min = 45.dp), label = "CurrencyContent", targetState = currencyMode, - transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, ) { targetCurrencyMode -> Column( horizontalAlignment = Alignment.End, From e18971e02a8f9ae196b811f69cd452df2cc17f59 Mon Sep 17 00:00:00 2001 From: Aleksandar Ilic Date: Tue, 24 Dec 2024 10:28:54 +0100 Subject: [PATCH 10/16] Implement UsdExchangeRateHandler --- app/detekt-baseline.xml | 1 + .../android/wallet/UsdExchangeRateHandler.kt | 29 +++++++++++++++++++ .../dashboard/WalletDashboardViewModel.kt | 28 +++++++++--------- .../details/TransactionDetailsViewModel.kt | 28 +++++++++--------- .../transactions/list/TransactionListItem.kt | 4 --- 5 files changed, 60 insertions(+), 30 deletions(-) create mode 100644 app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index 1d218e8c..74995f71 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -97,6 +97,7 @@ LongParameterList:NoteEditorViewModel.kt$NoteEditorViewModel$( @Assisted private val args: NoteEditorArgs, private val dispatcherProvider: CoroutineDispatcherProvider, private val fileAnalyser: FileAnalyser, private val activeAccountStore: ActiveAccountStore, private val feedRepository: FeedRepository, private val notePublishHandler: NotePublishHandler, private val attachmentRepository: AttachmentsRepository, private val exploreRepository: ExploreRepository, private val profileRepository: ProfileRepository, private val articleRepository: ArticleRepository, ) LongParameterList:ProfileDetailsViewModel.kt$ProfileDetailsViewModel$( savedStateHandle: SavedStateHandle, private val dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val feedsRepository: FeedsRepository, private val profileRepository: ProfileRepository, private val mutedUserRepository: MutedUserRepository, private val zapHandler: ZapHandler, ) LongParameterList:SubscriptionsManager.kt$SubscriptionsManager$( dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val userRepository: UserRepository, private val nostrNotary: NostrNotary, private val appConfigProvider: AppConfigProvider, @PrimalCacheApiClient private val cacheApiClient: PrimalApiClient, @PrimalWalletApiClient private val walletApiClient: PrimalApiClient, ) + LongParameterList:TransactionDetailsViewModel.kt$TransactionDetailsViewModel$( savedStateHandle: SavedStateHandle, private val activeAccountStore: ActiveAccountStore, private val dispatcherProvider: CoroutineDispatcherProvider, private val walletRepository: WalletRepository, private val feedRepository: FeedRepository, private val articleRepository: ArticleRepository, private val usdExchangeRateHandler: UsdExchangeRateHandler, ) LongParameterList:UserDataUpdater.kt$UserDataUpdater$( @Assisted val userId: String, private val settingsRepository: SettingsRepository, private val userRepository: UserRepository, private val walletRepository: WalletRepository, private val relayRepository: RelayRepository, private val bookmarksRepository: BookmarksRepository, private val premiumRepository: PremiumRepository, ) LongParameterList:UserRepository.kt$UserRepository$( private val database: PrimalDatabase, private val dispatchers: CoroutineDispatcherProvider, private val userAccountFetcher: UserAccountFetcher, private val accountsStore: UserAccountsStore, private val fileUploader: PrimalFileUploader, private val usersApi: UsersApi, private val nostrPublisher: NostrPublisher, ) LongParameterList:ZapHandler.kt$ZapHandler$( private val dispatcherProvider: CoroutineDispatcherProvider, private val accountsStore: UserAccountsStore, private val nwcNostrZapperFactory: NwcNostrZapperFactory, private val primalWalletZapper: WalletNostrZapper, private val relayRepository: RelayRepository, private val notary: NostrNotary, private val database: PrimalDatabase, ) diff --git a/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt b/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt new file mode 100644 index 00000000..c89bbbea --- /dev/null +++ b/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt @@ -0,0 +1,29 @@ +package net.primal.android.wallet + +import javax.inject.Inject +import javax.inject.Singleton +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.getAndUpdate +import net.primal.android.networking.sockets.errors.WssException +import net.primal.android.wallet.repository.WalletRepository +import timber.log.Timber + +@Singleton +class UsdExchangeRateHandler @Inject constructor( + private val walletRepository: WalletRepository, +) { + + private val _state = MutableStateFlow(value = 0.00) + val usdExchangeRate = _state.asStateFlow() + private fun setState(reducer: Double.() -> Double) = _state.getAndUpdate { it.reducer() } + + suspend fun updateExchangeRate(userId: String) { + try { + val btcRate = walletRepository.getExchangeRate(userId = userId) + setState { btcRate } + } catch (error: WssException) { + Timber.e(error) + } + } +} diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt index 0e0c9e62..5ad80e7e 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt @@ -22,6 +22,7 @@ import net.primal.android.user.accounts.active.ActiveAccountStore import net.primal.android.user.domain.WalletPreference import net.primal.android.user.repository.UserRepository import net.primal.android.user.subscriptions.SubscriptionsManager +import net.primal.android.wallet.UsdExchangeRateHandler import net.primal.android.wallet.dashboard.WalletDashboardContract.UiEvent import net.primal.android.wallet.dashboard.WalletDashboardContract.UiState import net.primal.android.wallet.db.WalletTransaction @@ -39,6 +40,7 @@ class WalletDashboardViewModel @Inject constructor( private val userRepository: UserRepository, private val primalBillingClient: PrimalBillingClient, private val subscriptionsManager: SubscriptionsManager, + private val usdExchangeRateHandler: UsdExchangeRateHandler, ) : ViewModel() { private val activeUserId = activeAccountStore.activeUserId() @@ -58,7 +60,7 @@ class WalletDashboardViewModel @Inject constructor( init { fetchWalletBalance() - fetchExchangeRate() + observeUsdExchangeRate() subscribeToEvents() subscribeToActiveAccount() subscribeToPurchases() @@ -117,21 +119,21 @@ class WalletDashboardViewModel @Inject constructor( } } - private fun fetchExchangeRate() = + private fun observeUsdExchangeRate() { viewModelScope.launch { - try { - val btcRate = walletRepository.getExchangeRate( - userId = activeAccountStore.activeUserId(), - ) - setState { - copy( - exchangeBtcUsdRate = btcRate, - ) - } - } catch (error: WssException) { - Timber.e(error) + fetchExchangeRate() + usdExchangeRateHandler.usdExchangeRate.collect { + setState { copy(exchangeBtcUsdRate = it) } } } + } + + private fun fetchExchangeRate() = + viewModelScope.launch { + usdExchangeRateHandler.updateExchangeRate( + userId = activeAccountStore.activeUserId(), + ) + } private fun enablePrimalWallet() = viewModelScope.launch { diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt index a39503b5..b742a3fd 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt @@ -23,6 +23,7 @@ import net.primal.android.notes.feed.model.asFeedPostUi import net.primal.android.notes.repository.FeedRepository import net.primal.android.premium.legend.asLegendaryCustomization import net.primal.android.user.accounts.active.ActiveAccountStore +import net.primal.android.wallet.UsdExchangeRateHandler import net.primal.android.wallet.db.WalletTransaction import net.primal.android.wallet.repository.WalletRepository import net.primal.android.wallet.transactions.details.TransactionDetailsContract.UiState @@ -37,6 +38,7 @@ class TransactionDetailsViewModel @Inject constructor( private val walletRepository: WalletRepository, private val feedRepository: FeedRepository, private val articleRepository: ArticleRepository, + private val usdExchangeRateHandler: UsdExchangeRateHandler, ) : ViewModel() { private val transactionId = savedStateHandle.transactionIdOrThrow @@ -48,7 +50,7 @@ class TransactionDetailsViewModel @Inject constructor( init { loadTransaction() - fetchExchangeRate() + observeUsdExchangeRate() } private fun loadTransaction() = @@ -113,21 +115,21 @@ class TransactionDetailsViewModel @Inject constructor( } } - private fun fetchExchangeRate() = + private fun observeUsdExchangeRate() { viewModelScope.launch { - try { - val btcRate = walletRepository.getExchangeRate( - userId = activeAccountStore.activeUserId(), - ) - setState { - copy( - currentExchangeRate = btcRate, - ) - } - } catch (error: WssException) { - Timber.e(error) + fetchExchangeRate() + usdExchangeRateHandler.usdExchangeRate.collect { + setState { copy(currentExchangeRate = it) } } } + } + + private fun fetchExchangeRate() = + viewModelScope.launch { + usdExchangeRateHandler.updateExchangeRate( + userId = activeAccountStore.activeUserId(), + ) + } private fun WalletTransaction.mapAsTransactionDataUi() = TransactionDetailDataUi( diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt index e6d70fcc..4d37f341 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/list/TransactionListItem.kt @@ -7,10 +7,6 @@ import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.togetherWith import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable From e0b9804de8a5f23569e78576af8242718c6b8b93 Mon Sep 17 00:00:00 2001 From: Aleksandar Ilic Date: Tue, 24 Dec 2024 11:08:24 +0100 Subject: [PATCH 11/16] Set default animation to wallet dashboard --- .../primal/android/wallet/dashboard/WalletDashboardScreen.kt | 4 +++- .../primal/android/wallet/dashboard/ui/WalletDashboard.kt | 5 ----- .../android/wallet/dashboard/ui/WalletDashboardLite.kt | 5 ----- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt index 85eeda92..4aad01c4 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardScreen.kt @@ -212,6 +212,7 @@ fun WalletDashboardScreen( .wrapContentSize(align = Alignment.Center) .padding(horizontal = 32.dp) .padding(vertical = 32.dp) + .animateContentSize() .then( if (state.primalWallet?.kycLevel == WalletKycLevel.None) { Modifier.graphicsLayer { alpha = DISABLED_WALLET_ALPHA } @@ -239,7 +240,8 @@ fun WalletDashboardScreen( .fillMaxWidth() .height(dashboardLiteHeightDp) .background(color = AppTheme.colorScheme.surface) - .padding(horizontal = 10.dp, vertical = 16.dp), + .padding(horizontal = 10.dp, vertical = 16.dp) + .animateContentSize(), walletBalance = state.walletBalance, actions = listOf(WalletAction.Send, WalletAction.Scan, WalletAction.Receive), onWalletAction = { action -> diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt index 253ce797..fe11dee9 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt @@ -1,10 +1,6 @@ package net.primal.android.wallet.dashboard.ui import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -43,7 +39,6 @@ fun WalletDashboard( modifier = modifier.fillMaxWidth(), label = "CurrencyContent", targetState = currencyMode, - transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, ) { targetCurrencyMode -> if (targetCurrencyMode == CurrencyMode.FIAT && exchangeBtcUsdRate != null) { FiatAmountText( diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt index 7c5f9aaf..18a304c7 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt @@ -1,10 +1,6 @@ package net.primal.android.wallet.dashboard.ui import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -36,7 +32,6 @@ fun WalletDashboardLite( modifier = Modifier, label = "CurrencyContent", targetState = currencyMode, - transitionSpec = { (slideInVertically() + fadeIn()) togetherWith fadeOut() }, ) { targetCurrencyMode -> if (targetCurrencyMode == CurrencyMode.FIAT && exchangeBtcUsdRate != null) { FiatAmountText( From 5c1dda6b1c51142f45c232dc1f6e661951c12c1c Mon Sep 17 00:00:00 2001 From: Aleksandar Ilic Date: Tue, 24 Dec 2024 11:13:27 +0100 Subject: [PATCH 12/16] Add isValidExchangeRate helper fun --- .../kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt | 2 ++ .../net/primal/android/wallet/dashboard/ui/WalletDashboard.kt | 3 ++- .../primal/android/wallet/dashboard/ui/WalletDashboardLite.kt | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt b/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt index c89bbbea..83cfdcb3 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt @@ -27,3 +27,5 @@ class UsdExchangeRateHandler @Inject constructor( } } } + +fun Double?.isValidExchangeRate() = this != null && this > 0 diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt index fe11dee9..d126733d 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal import net.primal.android.wallet.dashboard.CurrencyMode +import net.primal.android.wallet.isValidExchangeRate @Composable fun WalletDashboard( @@ -40,7 +41,7 @@ fun WalletDashboard( label = "CurrencyContent", targetState = currencyMode, ) { targetCurrencyMode -> - if (targetCurrencyMode == CurrencyMode.FIAT && exchangeBtcUsdRate != null) { + if (targetCurrencyMode == CurrencyMode.FIAT && exchangeBtcUsdRate.isValidExchangeRate()) { FiatAmountText( modifier = Modifier .wrapContentWidth() diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt index 18a304c7..cf0f1888 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal import net.primal.android.wallet.dashboard.CurrencyMode +import net.primal.android.wallet.isValidExchangeRate @Composable fun WalletDashboardLite( @@ -33,7 +34,7 @@ fun WalletDashboardLite( label = "CurrencyContent", targetState = currencyMode, ) { targetCurrencyMode -> - if (targetCurrencyMode == CurrencyMode.FIAT && exchangeBtcUsdRate != null) { + if (targetCurrencyMode == CurrencyMode.FIAT && exchangeBtcUsdRate.isValidExchangeRate()) { FiatAmountText( modifier = Modifier .graphicsLayer { From 1d70a6dc5e8b851c59861c095127f67a2711922e Mon Sep 17 00:00:00 2001 From: Aleksandar Ilic Date: Tue, 24 Dec 2024 11:14:59 +0100 Subject: [PATCH 13/16] Rename UsdExchangeRateHandler; Move ExchangeRateHandler to repository packer; --- .../android/wallet/dashboard/WalletDashboardViewModel.kt | 8 ++++---- .../primal/android/wallet/dashboard/ui/WalletDashboard.kt | 2 +- .../android/wallet/dashboard/ui/WalletDashboardLite.kt | 2 +- .../ExchangeRateHandler.kt} | 5 ++--- .../transactions/details/TransactionDetailsViewModel.kt | 8 ++++---- 5 files changed, 12 insertions(+), 13 deletions(-) rename app/src/main/kotlin/net/primal/android/wallet/{UsdExchangeRateHandler.kt => repository/ExchangeRateHandler.kt} (85%) diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt index 5ad80e7e..1025484a 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt @@ -22,7 +22,7 @@ import net.primal.android.user.accounts.active.ActiveAccountStore import net.primal.android.user.domain.WalletPreference import net.primal.android.user.repository.UserRepository import net.primal.android.user.subscriptions.SubscriptionsManager -import net.primal.android.wallet.UsdExchangeRateHandler +import net.primal.android.wallet.repository.ExchangeRateHandler import net.primal.android.wallet.dashboard.WalletDashboardContract.UiEvent import net.primal.android.wallet.dashboard.WalletDashboardContract.UiState import net.primal.android.wallet.db.WalletTransaction @@ -40,7 +40,7 @@ class WalletDashboardViewModel @Inject constructor( private val userRepository: UserRepository, private val primalBillingClient: PrimalBillingClient, private val subscriptionsManager: SubscriptionsManager, - private val usdExchangeRateHandler: UsdExchangeRateHandler, + private val exchangeRateHandler: ExchangeRateHandler, ) : ViewModel() { private val activeUserId = activeAccountStore.activeUserId() @@ -122,7 +122,7 @@ class WalletDashboardViewModel @Inject constructor( private fun observeUsdExchangeRate() { viewModelScope.launch { fetchExchangeRate() - usdExchangeRateHandler.usdExchangeRate.collect { + exchangeRateHandler.usdExchangeRate.collect { setState { copy(exchangeBtcUsdRate = it) } } } @@ -130,7 +130,7 @@ class WalletDashboardViewModel @Inject constructor( private fun fetchExchangeRate() = viewModelScope.launch { - usdExchangeRateHandler.updateExchangeRate( + exchangeRateHandler.updateExchangeRate( userId = activeAccountStore.activeUserId(), ) } diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt index d126733d..870621e9 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboard.kt @@ -16,7 +16,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal import net.primal.android.wallet.dashboard.CurrencyMode -import net.primal.android.wallet.isValidExchangeRate +import net.primal.android.wallet.repository.isValidExchangeRate @Composable fun WalletDashboard( diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt index cf0f1888..50501a9c 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/ui/WalletDashboardLite.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.math.BigDecimal import net.primal.android.wallet.dashboard.CurrencyMode -import net.primal.android.wallet.isValidExchangeRate +import net.primal.android.wallet.repository.isValidExchangeRate @Composable fun WalletDashboardLite( diff --git a/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt b/app/src/main/kotlin/net/primal/android/wallet/repository/ExchangeRateHandler.kt similarity index 85% rename from app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt rename to app/src/main/kotlin/net/primal/android/wallet/repository/ExchangeRateHandler.kt index 83cfdcb3..3c5f7e66 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/UsdExchangeRateHandler.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/repository/ExchangeRateHandler.kt @@ -1,4 +1,4 @@ -package net.primal.android.wallet +package net.primal.android.wallet.repository import javax.inject.Inject import javax.inject.Singleton @@ -6,11 +6,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.getAndUpdate import net.primal.android.networking.sockets.errors.WssException -import net.primal.android.wallet.repository.WalletRepository import timber.log.Timber @Singleton -class UsdExchangeRateHandler @Inject constructor( +class ExchangeRateHandler @Inject constructor( private val walletRepository: WalletRepository, ) { diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt index b742a3fd..a90f8af4 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt @@ -23,7 +23,7 @@ import net.primal.android.notes.feed.model.asFeedPostUi import net.primal.android.notes.repository.FeedRepository import net.primal.android.premium.legend.asLegendaryCustomization import net.primal.android.user.accounts.active.ActiveAccountStore -import net.primal.android.wallet.UsdExchangeRateHandler +import net.primal.android.wallet.repository.ExchangeRateHandler import net.primal.android.wallet.db.WalletTransaction import net.primal.android.wallet.repository.WalletRepository import net.primal.android.wallet.transactions.details.TransactionDetailsContract.UiState @@ -38,7 +38,7 @@ class TransactionDetailsViewModel @Inject constructor( private val walletRepository: WalletRepository, private val feedRepository: FeedRepository, private val articleRepository: ArticleRepository, - private val usdExchangeRateHandler: UsdExchangeRateHandler, + private val exchangeRateHandler: ExchangeRateHandler, ) : ViewModel() { private val transactionId = savedStateHandle.transactionIdOrThrow @@ -118,7 +118,7 @@ class TransactionDetailsViewModel @Inject constructor( private fun observeUsdExchangeRate() { viewModelScope.launch { fetchExchangeRate() - usdExchangeRateHandler.usdExchangeRate.collect { + exchangeRateHandler.usdExchangeRate.collect { setState { copy(currentExchangeRate = it) } } } @@ -126,7 +126,7 @@ class TransactionDetailsViewModel @Inject constructor( private fun fetchExchangeRate() = viewModelScope.launch { - usdExchangeRateHandler.updateExchangeRate( + exchangeRateHandler.updateExchangeRate( userId = activeAccountStore.activeUserId(), ) } From e88b924bccc62705f2444d39c98926505e2e214d Mon Sep 17 00:00:00 2001 From: Aleksandar Ilic Date: Tue, 24 Dec 2024 11:32:38 +0100 Subject: [PATCH 14/16] Update detekt baseline --- app/detekt-baseline.xml | 2 +- .../android/wallet/dashboard/WalletDashboardViewModel.kt | 2 +- .../transactions/details/TransactionDetailsViewModel.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index 74995f71..72b46d3f 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -97,7 +97,7 @@ LongParameterList:NoteEditorViewModel.kt$NoteEditorViewModel$( @Assisted private val args: NoteEditorArgs, private val dispatcherProvider: CoroutineDispatcherProvider, private val fileAnalyser: FileAnalyser, private val activeAccountStore: ActiveAccountStore, private val feedRepository: FeedRepository, private val notePublishHandler: NotePublishHandler, private val attachmentRepository: AttachmentsRepository, private val exploreRepository: ExploreRepository, private val profileRepository: ProfileRepository, private val articleRepository: ArticleRepository, ) LongParameterList:ProfileDetailsViewModel.kt$ProfileDetailsViewModel$( savedStateHandle: SavedStateHandle, private val dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val feedsRepository: FeedsRepository, private val profileRepository: ProfileRepository, private val mutedUserRepository: MutedUserRepository, private val zapHandler: ZapHandler, ) LongParameterList:SubscriptionsManager.kt$SubscriptionsManager$( dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val userRepository: UserRepository, private val nostrNotary: NostrNotary, private val appConfigProvider: AppConfigProvider, @PrimalCacheApiClient private val cacheApiClient: PrimalApiClient, @PrimalWalletApiClient private val walletApiClient: PrimalApiClient, ) - LongParameterList:TransactionDetailsViewModel.kt$TransactionDetailsViewModel$( savedStateHandle: SavedStateHandle, private val activeAccountStore: ActiveAccountStore, private val dispatcherProvider: CoroutineDispatcherProvider, private val walletRepository: WalletRepository, private val feedRepository: FeedRepository, private val articleRepository: ArticleRepository, private val usdExchangeRateHandler: UsdExchangeRateHandler, ) + LongParameterList:TransactionDetailsViewModel.kt$TransactionDetailsViewModel$( savedStateHandle: SavedStateHandle, private val dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val walletRepository: WalletRepository, private val feedRepository: FeedRepository, private val articleRepository: ArticleRepository, private val exchangeRateHandler: ExchangeRateHandler, ) LongParameterList:UserDataUpdater.kt$UserDataUpdater$( @Assisted val userId: String, private val settingsRepository: SettingsRepository, private val userRepository: UserRepository, private val walletRepository: WalletRepository, private val relayRepository: RelayRepository, private val bookmarksRepository: BookmarksRepository, private val premiumRepository: PremiumRepository, ) LongParameterList:UserRepository.kt$UserRepository$( private val database: PrimalDatabase, private val dispatchers: CoroutineDispatcherProvider, private val userAccountFetcher: UserAccountFetcher, private val accountsStore: UserAccountsStore, private val fileUploader: PrimalFileUploader, private val usersApi: UsersApi, private val nostrPublisher: NostrPublisher, ) LongParameterList:ZapHandler.kt$ZapHandler$( private val dispatcherProvider: CoroutineDispatcherProvider, private val accountsStore: UserAccountsStore, private val nwcNostrZapperFactory: NwcNostrZapperFactory, private val primalWalletZapper: WalletNostrZapper, private val relayRepository: RelayRepository, private val notary: NostrNotary, private val database: PrimalDatabase, ) diff --git a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt index 1025484a..c5680bdb 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/dashboard/WalletDashboardViewModel.kt @@ -22,10 +22,10 @@ import net.primal.android.user.accounts.active.ActiveAccountStore import net.primal.android.user.domain.WalletPreference import net.primal.android.user.repository.UserRepository import net.primal.android.user.subscriptions.SubscriptionsManager -import net.primal.android.wallet.repository.ExchangeRateHandler import net.primal.android.wallet.dashboard.WalletDashboardContract.UiEvent import net.primal.android.wallet.dashboard.WalletDashboardContract.UiState import net.primal.android.wallet.db.WalletTransaction +import net.primal.android.wallet.repository.ExchangeRateHandler import net.primal.android.wallet.repository.WalletRepository import net.primal.android.wallet.store.PrimalBillingClient import net.primal.android.wallet.store.domain.SatsPurchase diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt index a90f8af4..25e0b6a5 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsViewModel.kt @@ -23,8 +23,8 @@ import net.primal.android.notes.feed.model.asFeedPostUi import net.primal.android.notes.repository.FeedRepository import net.primal.android.premium.legend.asLegendaryCustomization import net.primal.android.user.accounts.active.ActiveAccountStore -import net.primal.android.wallet.repository.ExchangeRateHandler import net.primal.android.wallet.db.WalletTransaction +import net.primal.android.wallet.repository.ExchangeRateHandler import net.primal.android.wallet.repository.WalletRepository import net.primal.android.wallet.transactions.details.TransactionDetailsContract.UiState import net.primal.android.wallet.utils.CurrencyConversionUtils.toSats @@ -33,8 +33,8 @@ import timber.log.Timber @HiltViewModel class TransactionDetailsViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - private val activeAccountStore: ActiveAccountStore, private val dispatcherProvider: CoroutineDispatcherProvider, + private val activeAccountStore: ActiveAccountStore, private val walletRepository: WalletRepository, private val feedRepository: FeedRepository, private val articleRepository: ArticleRepository, From 660c4799af78d47b017a7a47ce69b26c9396df68 Mon Sep 17 00:00:00 2001 From: Aleksandar Ilic Date: Tue, 24 Dec 2024 11:35:26 +0100 Subject: [PATCH 15/16] Add change currency icon --- .../core/compose/icons/__PrimalIcons.kt | 2 + .../primaliconpack/WalletChangeCurrency.kt | 109 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 app/src/main/kotlin/net/primal/android/core/compose/icons/primaliconpack/WalletChangeCurrency.kt diff --git a/app/src/main/kotlin/net/primal/android/core/compose/icons/__PrimalIcons.kt b/app/src/main/kotlin/net/primal/android/core/compose/icons/__PrimalIcons.kt index a4ed259b..5676396d 100644 --- a/app/src/main/kotlin/net/primal/android/core/compose/icons/__PrimalIcons.kt +++ b/app/src/main/kotlin/net/primal/android/core/compose/icons/__PrimalIcons.kt @@ -130,6 +130,7 @@ import net.primal.android.core.compose.icons.primaliconpack.UserFeedRemove import net.primal.android.core.compose.icons.primaliconpack.Verified import net.primal.android.core.compose.icons.primaliconpack.VerifiedFilled import net.primal.android.core.compose.icons.primaliconpack.WalletBitcoinPayment +import net.primal.android.core.compose.icons.primaliconpack.WalletChangeCurrency import net.primal.android.core.compose.icons.primaliconpack.WalletError import net.primal.android.core.compose.icons.primaliconpack.WalletLightningPayment import net.primal.android.core.compose.icons.primaliconpack.WalletLightningPaymentAlt @@ -293,6 +294,7 @@ val PrimalIcons.PrimalIcons: ____KtList DrawerSignOut, OnboardingZapsExplained, Document, + WalletChangeCurrency, ) return __PrimalIcons!! } diff --git a/app/src/main/kotlin/net/primal/android/core/compose/icons/primaliconpack/WalletChangeCurrency.kt b/app/src/main/kotlin/net/primal/android/core/compose/icons/primaliconpack/WalletChangeCurrency.kt new file mode 100644 index 00000000..f731b049 --- /dev/null +++ b/app/src/main/kotlin/net/primal/android/core/compose/icons/primaliconpack/WalletChangeCurrency.kt @@ -0,0 +1,109 @@ +package net.primal.android.core.compose.icons.primaliconpack + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp +import net.primal.android.core.compose.icons.PrimalIcons + +val PrimalIcons.WalletChangeCurrency: ImageVector + get() { + if (_WalletChangeCurrency != null) { + return _WalletChangeCurrency!! + } + _WalletChangeCurrency = ImageVector.Builder( + name = "WalletChangeCurrency", + defaultWidth = 18.dp, + defaultHeight = 14.dp, + viewportWidth = 18f, + viewportHeight = 14f + ).apply { + path( + fill = SolidColor(Color(0xFFCA079F)), + pathFillType = PathFillType.EvenOdd + ) { + moveTo(0.224f, 3.919f) + curveTo(-0.075f, 4.207f, -0.075f, 4.673f, 0.224f, 4.961f) + curveTo(0.522f, 5.248f, 1.005f, 5.248f, 1.304f, 4.961f) + lineTo(3.542f, 2.802f) + verticalLineTo(11.651f) + curveTo(3.542f, 12.058f, 3.885f, 12.389f, 4.307f, 12.389f) + curveTo(4.73f, 12.389f, 5.072f, 12.058f, 5.072f, 11.651f) + verticalLineTo(2.605f) + lineTo(7.516f, 4.961f) + curveTo(7.814f, 5.248f, 8.297f, 5.248f, 8.596f, 4.961f) + curveTo(8.894f, 4.673f, 8.894f, 4.207f, 8.596f, 3.919f) + lineTo(4.616f, 0.083f) + curveTo(4.502f, -0.028f, 4.317f, -0.028f, 4.203f, 0.083f) + lineTo(0.224f, 3.919f) + close() + } + path( + fill = SolidColor(Color(0xFFCA077C)), + pathFillType = PathFillType.EvenOdd + ) { + moveTo(0.224f, 3.919f) + curveTo(-0.075f, 4.207f, -0.075f, 4.673f, 0.224f, 4.961f) + curveTo(0.522f, 5.248f, 1.005f, 5.248f, 1.304f, 4.961f) + lineTo(3.542f, 2.802f) + verticalLineTo(11.651f) + curveTo(3.542f, 12.058f, 3.885f, 12.389f, 4.307f, 12.389f) + curveTo(4.73f, 12.389f, 5.072f, 12.058f, 5.072f, 11.651f) + verticalLineTo(2.605f) + lineTo(7.516f, 4.961f) + curveTo(7.814f, 5.248f, 8.297f, 5.248f, 8.596f, 4.961f) + curveTo(8.894f, 4.673f, 8.894f, 4.207f, 8.596f, 3.919f) + lineTo(4.616f, 0.083f) + curveTo(4.502f, -0.028f, 4.317f, -0.028f, 4.203f, 0.083f) + lineTo(0.224f, 3.919f) + close() + } + path( + fill = SolidColor(Color(0xFFCA079F)), + pathFillType = PathFillType.EvenOdd + ) { + moveTo(17.776f, 10.08f) + curveTo(18.075f, 9.793f, 18.075f, 9.327f, 17.776f, 9.039f) + curveTo(17.478f, 8.752f, 16.995f, 8.752f, 16.696f, 9.039f) + lineTo(14.253f, 11.395f) + verticalLineTo(2.349f) + curveTo(14.253f, 1.942f, 13.911f, 1.611f, 13.488f, 1.611f) + curveTo(13.066f, 1.611f, 12.723f, 1.942f, 12.723f, 2.349f) + verticalLineTo(11.198f) + lineTo(10.484f, 9.039f) + curveTo(10.186f, 8.752f, 9.703f, 8.752f, 9.404f, 9.039f) + curveTo(9.106f, 9.327f, 9.106f, 9.793f, 9.404f, 10.08f) + lineTo(13.384f, 13.917f) + curveTo(13.498f, 14.028f, 13.683f, 14.028f, 13.797f, 13.917f) + lineTo(17.776f, 10.08f) + close() + } + path( + fill = SolidColor(Color(0xFFCA077C)), + pathFillType = PathFillType.EvenOdd + ) { + moveTo(17.776f, 10.08f) + curveTo(18.075f, 9.793f, 18.075f, 9.327f, 17.776f, 9.039f) + curveTo(17.478f, 8.752f, 16.995f, 8.752f, 16.696f, 9.039f) + lineTo(14.253f, 11.395f) + verticalLineTo(2.349f) + curveTo(14.253f, 1.942f, 13.911f, 1.611f, 13.488f, 1.611f) + curveTo(13.066f, 1.611f, 12.723f, 1.942f, 12.723f, 2.349f) + verticalLineTo(11.198f) + lineTo(10.484f, 9.039f) + curveTo(10.186f, 8.752f, 9.703f, 8.752f, 9.404f, 9.039f) + curveTo(9.106f, 9.327f, 9.106f, 9.793f, 9.404f, 10.08f) + lineTo(13.384f, 13.917f) + curveTo(13.498f, 14.028f, 13.683f, 14.028f, 13.797f, 13.917f) + lineTo(17.776f, 10.08f) + close() + } + }.build() + + return _WalletChangeCurrency!! + } + +@Suppress("ObjectPropertyName") +private var _WalletChangeCurrency: ImageVector? = null From 0c7fe4570fe5d2190a4a6759f2dcdb1156399f39 Mon Sep 17 00:00:00 2001 From: Mehmedalija Karisik Date: Wed, 25 Dec 2024 10:58:30 +0100 Subject: [PATCH 16/16] Add isValidExchangeRate check for currenct USD value --- .../details/TransactionDetailsScreen.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt index 2888e72f..a3f537d6 100644 --- a/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt +++ b/app/src/main/kotlin/net/primal/android/wallet/transactions/details/TransactionDetailsScreen.kt @@ -107,6 +107,7 @@ import net.primal.android.theme.AppTheme import net.primal.android.wallet.dashboard.ui.BtcAmountText import net.primal.android.wallet.domain.TxState import net.primal.android.wallet.domain.TxType +import net.primal.android.wallet.repository.isValidExchangeRate import net.primal.android.wallet.transactions.details.TransactionDetailsContract.UiState import net.primal.android.wallet.transactions.list.TransactionIcon import net.primal.android.wallet.utils.CurrencyConversionUtils.toBtc @@ -561,12 +562,14 @@ private fun ColumnScope.TransactionUsdValues( ) } - numberFormat.formatSafely(currentUsdAmount)?.let { formattedUsdAmount -> - PrimalDivider() - TransactionDetailListItem( - section = stringResource(id = R.string.wallet_transaction_details_currents_usd_item), - value = "$$formattedUsdAmount", - ) + if (currentExchangeRate.isValidExchangeRate()) { + numberFormat.formatSafely(currentUsdAmount)?.let { formattedUsdAmount -> + PrimalDivider() + TransactionDetailListItem( + section = stringResource(id = R.string.wallet_transaction_details_currents_usd_item), + value = "$$formattedUsdAmount", + ) + } } }