Exchange Rate redesign
This commit is contained in:
parent
77f1c163ad
commit
c03c595b28
|
@ -0,0 +1,27 @@
|
|||
package co.electriccoin.zcash.preference.model.entry
|
||||
|
||||
import co.electriccoin.zcash.preference.api.PreferenceProvider
|
||||
|
||||
data class NullableBooleanPreferenceDefault(
|
||||
override val key: PreferenceKey,
|
||||
private val defaultValue: Boolean?
|
||||
) : PreferenceDefault<Boolean?> {
|
||||
@Suppress("SwallowedException")
|
||||
override suspend fun getValue(preferenceProvider: PreferenceProvider) =
|
||||
preferenceProvider.getString(key)?.let {
|
||||
try {
|
||||
it.toBooleanStrict()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// TODO [#32]: Log coercion failure instead of just silently returning default
|
||||
// TODO [#32]: https://github.com/Electric-Coin-Company/zashi-android/issues/32
|
||||
defaultValue
|
||||
}
|
||||
} ?: defaultValue
|
||||
|
||||
override suspend fun putValue(
|
||||
preferenceProvider: PreferenceProvider,
|
||||
newValue: Boolean?
|
||||
) {
|
||||
preferenceProvider.putString(key, newValue.toString())
|
||||
}
|
||||
}
|
|
@ -11,6 +11,18 @@ data class ZashiColors(
|
|||
val textLight: Color,
|
||||
val textLightSupport: Color,
|
||||
val surfacePrimary: Color,
|
||||
val bgPrimary: Color,
|
||||
val defaultFg: Color,
|
||||
val textTertiary: Color,
|
||||
val textPrimary: Color,
|
||||
val strokeSecondary: Color,
|
||||
val btnTertiaryBg: Color,
|
||||
val btnTertiaryFg: Color,
|
||||
val btnPrimaryBg: Color,
|
||||
val btnPrimaryBgDisabled: Color,
|
||||
val btnPrimaryFg: Color,
|
||||
val btnPrimaryFgDisabled: Color,
|
||||
val btnTextFg: Color,
|
||||
)
|
||||
|
||||
internal val LightZashiColorPalette =
|
||||
|
@ -18,6 +30,18 @@ internal val LightZashiColorPalette =
|
|||
textLight = Color(0xFFFFFFFF),
|
||||
textLightSupport = Color(0xFFD9D8CF),
|
||||
surfacePrimary = Color(0xFF282622),
|
||||
bgPrimary = Color(0xFFFFFFFF),
|
||||
defaultFg = Color(0xFFD9D8CF),
|
||||
textPrimary = Color(0xFF231F20),
|
||||
textTertiary = Color(0xFF716C5D),
|
||||
strokeSecondary = Color(0xFFEBEBE6),
|
||||
btnTertiaryBg = Color(0xFFEBEBE6),
|
||||
btnTertiaryFg = Color(0xFF4D4941),
|
||||
btnPrimaryBg = Color(0xFF231F20),
|
||||
btnPrimaryBgDisabled = Color(0xFFEBEBE6),
|
||||
btnPrimaryFg = Color(0xFFFFFFFF),
|
||||
btnPrimaryFgDisabled = Color(0xFF94907B),
|
||||
btnTextFg = Color(0xFF231F20)
|
||||
)
|
||||
|
||||
internal val DarkZashiColorPalette =
|
||||
|
@ -25,6 +49,18 @@ internal val DarkZashiColorPalette =
|
|||
textLight = Color(0xFFE8E8E8),
|
||||
textLightSupport = Color(0xFFBDBBBC),
|
||||
surfacePrimary = Color(0xFF454243),
|
||||
bgPrimary = Color(0xFF231F20),
|
||||
defaultFg = Color(0xFFBDBBBC),
|
||||
textPrimary = Color(0xFFE8E8E8),
|
||||
textTertiary = Color(0xFFBDBBBC),
|
||||
strokeSecondary = Color(0xFF454243),
|
||||
btnTertiaryBg = Color(0xFF343031),
|
||||
btnTertiaryFg = Color(0xFFD2D1D2),
|
||||
btnPrimaryBg = Color(0xFFFFFFFF),
|
||||
btnPrimaryBgDisabled = Color(0xFF343031),
|
||||
btnPrimaryFg = Color(0xFF231F20),
|
||||
btnPrimaryFgDisabled = Color(0xFF7E7C7C),
|
||||
btnTextFg = Color(0xFFE8E8E8)
|
||||
)
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
|
|
|
@ -56,6 +56,7 @@ android {
|
|||
"src/main/res/ui/wallet_address",
|
||||
"src/main/res/ui/warning",
|
||||
"src/main/res/ui/whats_new",
|
||||
"src/main/res/ui/exchange_rate",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package co.electriccoin.zcash.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
|
@ -27,6 +28,7 @@ import co.electriccoin.zcash.ui.NavigationTargets.ABOUT
|
|||
import co.electriccoin.zcash.ui.NavigationTargets.ADVANCED_SETTINGS
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.CHOOSE_SERVER
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.DELETE_WALLET
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.EXCHANGE_RATE_OPT_IN
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.EXPORT_PRIVATE_DATA
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.HOME
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.NOT_ENOUGH_SPACE
|
||||
|
@ -34,6 +36,7 @@ import co.electriccoin.zcash.ui.NavigationTargets.SCAN
|
|||
import co.electriccoin.zcash.ui.NavigationTargets.SEED_RECOVERY
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SEND_CONFIRMATION
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SETTINGS
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SETTINGS_EXCHANGE_RATE_OPT_IN
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.WHATS_NEW
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalNavController
|
||||
|
@ -51,6 +54,8 @@ import co.electriccoin.zcash.ui.screen.authentication.WrapAuthentication
|
|||
import co.electriccoin.zcash.ui.screen.chooseserver.WrapChooseServer
|
||||
import co.electriccoin.zcash.ui.screen.deletewallet.WrapDeleteWallet
|
||||
import co.electriccoin.zcash.ui.screen.disconnected.WrapDisconnected
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.optin.AndroidExchangeRateOptIn
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.settings.AndroidSettingsExchangeRateOptIn
|
||||
import co.electriccoin.zcash.ui.screen.exportdata.WrapExportPrivateData
|
||||
import co.electriccoin.zcash.ui.screen.home.WrapHome
|
||||
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
|
||||
|
@ -86,6 +91,18 @@ internal fun MainActivity.Navigation() {
|
|||
val (deleteWalletAuthentication, setDeleteWalletAuthentication) =
|
||||
rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
walletViewModel.navigationCommand.collect {
|
||||
navController.navigateJustOnce(it)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
walletViewModel.backNavigationCommand.collect {
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = HOME,
|
||||
|
@ -145,6 +162,9 @@ internal fun MainActivity.Navigation() {
|
|||
unProtectedDestination = DELETE_WALLET
|
||||
)
|
||||
},
|
||||
onCurrencyConversion = {
|
||||
navController.navigateJustOnce(SETTINGS_EXCHANGE_RATE_OPT_IN)
|
||||
}
|
||||
)
|
||||
|
||||
when {
|
||||
|
@ -214,6 +234,12 @@ internal fun MainActivity.Navigation() {
|
|||
composable(WHATS_NEW) {
|
||||
WrapWhatsNew()
|
||||
}
|
||||
composable(EXCHANGE_RATE_OPT_IN) {
|
||||
AndroidExchangeRateOptIn()
|
||||
}
|
||||
composable(SETTINGS_EXCHANGE_RATE_OPT_IN) {
|
||||
AndroidSettingsExchangeRateOptIn()
|
||||
}
|
||||
composable(SCAN) {
|
||||
WrapScanValidator(
|
||||
onScanValid = { scanResult ->
|
||||
|
@ -445,4 +471,6 @@ object NavigationTargets {
|
|||
const val SETTINGS = "settings"
|
||||
const val SUPPORT = "support"
|
||||
const val WHATS_NEW = "whats_new"
|
||||
const val SETTINGS_EXCHANGE_RATE_OPT_IN = "settings_exchange_rate_opt_in"
|
||||
const val EXCHANGE_RATE_OPT_IN = "EXCHANGE_RATE_OPT_IN"
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import androidx.compose.ui.unit.dp
|
|||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.sdk.extension.toZecStringFull
|
||||
import cash.z.ecc.sdk.type.ZcashCurrency
|
||||
import co.electriccoin.zcash.ui.StyledExchangeBalance
|
||||
import co.electriccoin.zcash.ui.common.extension.asZecAmountTriple
|
||||
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
|
||||
import co.electriccoin.zcash.ui.design.R
|
||||
|
@ -35,6 +34,7 @@ import co.electriccoin.zcash.ui.design.component.StyledBalanceDefaults
|
|||
import co.electriccoin.zcash.ui.design.component.ZecAmountTriple
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.ObserveFiatCurrencyResultFixture
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.widget.StyledExchangeBalance
|
||||
|
||||
@Preview(device = Devices.PIXEL_2)
|
||||
@Composable
|
||||
|
@ -151,7 +151,9 @@ fun BalanceWidget(
|
|||
parts = balanceState.totalBalance.toZecStringFull().asZecAmountTriple()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
if (balanceState.exchangeRate is ExchangeRateState.Data) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
StyledExchangeBalance(
|
||||
zatoshi = balanceState.totalBalance,
|
||||
|
@ -159,7 +161,9 @@ fun BalanceWidget(
|
|||
isHideBalances = isHideBalances
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
if (balanceState.exchangeRate is ExchangeRateState.Data) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (isReferenceToBalances) {
|
||||
|
|
|
@ -26,8 +26,10 @@ import cash.z.ecc.android.sdk.model.ZcashNetwork
|
|||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import cash.z.ecc.sdk.type.fromResources
|
||||
import co.electriccoin.zcash.global.getInstance
|
||||
import co.electriccoin.zcash.preference.model.entry.NullableBooleanPreferenceDefault
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.EXCHANGE_RATE_OPT_IN
|
||||
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceState
|
||||
import co.electriccoin.zcash.ui.common.extension.throttle
|
||||
|
@ -55,6 +57,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
|
@ -78,7 +81,6 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
// To make this more multiplatform compatible, we need to remove the dependency on Context
|
||||
|
@ -97,6 +99,10 @@ class WalletViewModel(
|
|||
*/
|
||||
private val persistWalletMutex = Mutex()
|
||||
|
||||
val navigationCommand = MutableSharedFlow<String>()
|
||||
|
||||
val backNavigationCommand = MutableSharedFlow<Unit>()
|
||||
|
||||
/**
|
||||
* Synchronizer that is retained long enough to survive configuration changes.
|
||||
*/
|
||||
|
@ -292,18 +298,25 @@ class WalletViewModel(
|
|||
initialValue = TransactionHistorySyncState.Loading
|
||||
)
|
||||
|
||||
val isExchangeRateUsdOptedIn = nullableBooleanStateFlow(StandardPreferenceKeys.EXCHANGE_RATE_USD_OPTED_IN)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val exchangeRateUsdInternal =
|
||||
synchronizer
|
||||
.filterNotNull()
|
||||
.flatMapLatest { synchronizer ->
|
||||
synchronizer.exchangeRateUsd
|
||||
isExchangeRateUsdOptedIn.flatMapLatest { optedIn ->
|
||||
if (optedIn == true) {
|
||||
synchronizer
|
||||
.filterNotNull()
|
||||
.flatMapLatest { synchronizer ->
|
||||
synchronizer.exchangeRateUsd
|
||||
}
|
||||
} else {
|
||||
flowOf(ObserveFiatCurrencyResult(isLoading = false, currencyConversion = null))
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(USD_EXCHANGE_REFRESH_LOCK_THRESHOLD),
|
||||
initialValue = ObserveFiatCurrencyResult()
|
||||
)
|
||||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(USD_EXCHANGE_REFRESH_LOCK_THRESHOLD),
|
||||
initialValue = ObserveFiatCurrencyResult(isLoading = false, currencyConversion = null)
|
||||
)
|
||||
|
||||
private val usdExchangeRateTimestamp =
|
||||
exchangeRateUsdInternal
|
||||
|
@ -312,23 +325,56 @@ class WalletViewModel(
|
|||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
private var lastExchangeRateUsdValue: ExchangeRateState = ExchangeRateState.OptedOut
|
||||
|
||||
val exchangeRateUsd: StateFlow<ExchangeRateState> =
|
||||
channelFlow {
|
||||
var lastValue = ExchangeRateState(onRefresh = ::refreshExchangeRateUsd)
|
||||
|
||||
combine(
|
||||
isExchangeRateUsdOptedIn,
|
||||
exchangeRateUsdInternal,
|
||||
staleExchangeRateUsdLock.state,
|
||||
refreshExchangeRateUsdLock.state,
|
||||
) { exchangeRate, isStale, isRefreshEnabled ->
|
||||
lastValue =
|
||||
lastValue.copy(
|
||||
isLoading = exchangeRate.isLoading,
|
||||
isStale = isStale,
|
||||
isRefreshEnabled = isRefreshEnabled,
|
||||
currencyConversion = exchangeRate.currencyConversion,
|
||||
)
|
||||
lastValue
|
||||
) { isOptedIn, exchangeRate, isStale, isRefreshEnabled ->
|
||||
lastExchangeRateUsdValue =
|
||||
when (isOptedIn) {
|
||||
true ->
|
||||
when (val lastValue = lastExchangeRateUsdValue) {
|
||||
is ExchangeRateState.Data ->
|
||||
lastValue.copy(
|
||||
isLoading = exchangeRate.isLoading,
|
||||
isStale = isStale,
|
||||
isRefreshEnabled = isRefreshEnabled,
|
||||
currencyConversion = exchangeRate.currencyConversion,
|
||||
)
|
||||
|
||||
ExchangeRateState.OptedOut ->
|
||||
ExchangeRateState.Data(
|
||||
isLoading = exchangeRate.isLoading,
|
||||
isStale = isStale,
|
||||
isRefreshEnabled = isRefreshEnabled,
|
||||
currencyConversion = exchangeRate.currencyConversion,
|
||||
onRefresh = ::refreshExchangeRateUsd
|
||||
)
|
||||
|
||||
is ExchangeRateState.OptIn ->
|
||||
ExchangeRateState.Data(
|
||||
isLoading = exchangeRate.isLoading,
|
||||
isStale = isStale,
|
||||
isRefreshEnabled = isRefreshEnabled,
|
||||
currencyConversion = exchangeRate.currencyConversion,
|
||||
onRefresh = ::refreshExchangeRateUsd
|
||||
)
|
||||
}
|
||||
|
||||
false -> ExchangeRateState.OptedOut
|
||||
null ->
|
||||
ExchangeRateState.OptIn(
|
||||
onDismissClick = ::dismissWidgetOptInExchangeRateUsd,
|
||||
onPrimaryClick = ::showOptInExchangeRateUsd
|
||||
)
|
||||
}
|
||||
|
||||
lastExchangeRateUsdValue
|
||||
}.distinctUntilChanged()
|
||||
.onEach {
|
||||
Twig.info { "[USD] $it" }
|
||||
|
@ -342,7 +388,7 @@ class WalletViewModel(
|
|||
}.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
initialValue = ExchangeRateState(onRefresh = ::refreshExchangeRateUsd)
|
||||
initialValue = ExchangeRateState.OptedOut
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -380,7 +426,7 @@ class WalletViewModel(
|
|||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
BalanceState.None(ExchangeRateState(onRefresh = ::refreshExchangeRateUsd))
|
||||
BalanceState.None(ExchangeRateState.OptedOut)
|
||||
)
|
||||
|
||||
private val refreshExchangeRateUsdLock =
|
||||
|
@ -400,11 +446,32 @@ class WalletViewModel(
|
|||
viewModelScope.launch {
|
||||
val synchronizer = synchronizer.filterNotNull().first()
|
||||
val value = exchangeRateUsd.value
|
||||
if (value.isRefreshEnabled && !value.isLoading) {
|
||||
if (value is ExchangeRateState.Data && value.isRefreshEnabled && !value.isLoading) {
|
||||
synchronizer.refreshExchangeRateUsd()
|
||||
}
|
||||
}
|
||||
|
||||
fun optInExchangeRateUsd(optIn: Boolean) =
|
||||
viewModelScope.launch {
|
||||
setNullableBooleanPreference(StandardPreferenceKeys.EXCHANGE_RATE_USD_OPTED_IN, optIn)
|
||||
backNavigationCommand.emit(Unit)
|
||||
}
|
||||
|
||||
fun dismissOptInExchangeRateUsd() =
|
||||
viewModelScope.launch {
|
||||
setNullableBooleanPreference(StandardPreferenceKeys.EXCHANGE_RATE_USD_OPTED_IN, false)
|
||||
backNavigationCommand.emit(Unit)
|
||||
}
|
||||
|
||||
private fun dismissWidgetOptInExchangeRateUsd() {
|
||||
setNullableBooleanPreference(StandardPreferenceKeys.EXCHANGE_RATE_USD_OPTED_IN, false)
|
||||
}
|
||||
|
||||
private fun showOptInExchangeRateUsd() =
|
||||
viewModelScope.launch {
|
||||
navigationCommand.emit(EXCHANGE_RATE_OPT_IN)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wallet asynchronously and then persists it. Clients observe
|
||||
* [secretState] to see the side effects. This would be used for a user creating a new wallet.
|
||||
|
@ -579,6 +646,26 @@ class WalletViewModel(
|
|||
// Nothing to close
|
||||
}
|
||||
}
|
||||
|
||||
private fun nullableBooleanStateFlow(default: NullableBooleanPreferenceDefault): StateFlow<Boolean?> =
|
||||
flow {
|
||||
val preferenceProvider = StandardPreferenceSingleton.getInstance(getApplication())
|
||||
emitAll(default.observe(preferenceProvider))
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
null
|
||||
)
|
||||
|
||||
private fun setNullableBooleanPreference(
|
||||
default: NullableBooleanPreferenceDefault,
|
||||
newState: Boolean
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
val prefs = StandardPreferenceSingleton.getInstance(getApplication())
|
||||
default.putValue(prefs, newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -718,5 +805,5 @@ fun Synchronizer.Status.isSyncing() = this == Synchronizer.Status.SYNCING
|
|||
|
||||
fun Synchronizer.Status.isSynced() = this == Synchronizer.Status.SYNCED
|
||||
|
||||
private val USD_EXCHANGE_REFRESH_LOCK_THRESHOLD = 2.minutes
|
||||
private val USD_EXCHANGE_STALE_LOCK_THRESHOLD = 15.minutes
|
||||
private val USD_EXCHANGE_REFRESH_LOCK_THRESHOLD = 10.seconds
|
||||
private val USD_EXCHANGE_STALE_LOCK_THRESHOLD = 20.seconds
|
||||
|
|
|
@ -3,13 +3,20 @@ package co.electriccoin.zcash.ui.common.wallet
|
|||
import cash.z.ecc.android.sdk.model.FiatCurrency
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
|
||||
|
||||
data class ExchangeRateState(
|
||||
val isLoading: Boolean = true,
|
||||
val isStale: Boolean = false,
|
||||
val isRefreshEnabled: Boolean = true,
|
||||
val currencyConversion: FiatCurrencyConversion? = null,
|
||||
val onRefresh: () -> Unit
|
||||
) {
|
||||
val fiatCurrency: FiatCurrency
|
||||
get() = FiatCurrency.USD
|
||||
sealed interface ExchangeRateState {
|
||||
data class Data(
|
||||
val isLoading: Boolean = true,
|
||||
val isStale: Boolean = false,
|
||||
val isRefreshEnabled: Boolean = true,
|
||||
val currencyConversion: FiatCurrencyConversion? = null,
|
||||
val fiatCurrency: FiatCurrency = FiatCurrency.USD,
|
||||
val onRefresh: () -> Unit,
|
||||
) : ExchangeRateState
|
||||
|
||||
data class OptIn(
|
||||
val onDismissClick: () -> Unit = {},
|
||||
val onPrimaryClick: () -> Unit = {}
|
||||
) : ExchangeRateState
|
||||
|
||||
data object OptedOut : ExchangeRateState
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@ package co.electriccoin.zcash.ui.fixture
|
|||
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
|
||||
|
||||
object ExchangeRateStateFixture {
|
||||
fun new() = ExchangeRateState {}
|
||||
fun new() = ExchangeRateState.OptedOut
|
||||
}
|
||||
|
|
|
@ -14,5 +14,5 @@ object ObserveFiatCurrencyResultFixture {
|
|||
timestamp = Clock.System.now(),
|
||||
priceOfZec = 25.0
|
||||
),
|
||||
) = ExchangeRateState(isLoading, isStale, isRefreshEnabled, currencyConversion) {}
|
||||
) = ExchangeRateState.Data(isLoading, isStale, isRefreshEnabled, currencyConversion) {}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package co.electriccoin.zcash.ui.preference
|
|||
|
||||
import co.electriccoin.zcash.preference.model.entry.BooleanPreferenceDefault
|
||||
import co.electriccoin.zcash.preference.model.entry.IntegerPreferenceDefault
|
||||
import co.electriccoin.zcash.preference.model.entry.NullableBooleanPreferenceDefault
|
||||
import co.electriccoin.zcash.preference.model.entry.PreferenceKey
|
||||
import co.electriccoin.zcash.ui.common.model.OnboardingState
|
||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||
|
@ -68,4 +69,9 @@ object StandardPreferenceKeys {
|
|||
PreferenceKey("IS_HIDE_BALANCES"),
|
||||
false
|
||||
)
|
||||
val EXCHANGE_RATE_USD_OPTED_IN =
|
||||
NullableBooleanPreferenceDefault(
|
||||
PreferenceKey("EXCHANGE_RATE_USD_OPTED_IN"),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
package co.electriccoin.zcash.ui.screen.account.view
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
@ -10,6 +18,11 @@ import androidx.compose.material3.IconButton
|
|||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
@ -17,7 +30,7 @@ import androidx.compose.ui.platform.testTag
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import co.electriccoin.zcash.ui.R
|
||||
|
@ -39,14 +52,22 @@ import co.electriccoin.zcash.ui.screen.account.AccountTag
|
|||
import co.electriccoin.zcash.ui.screen.account.fixture.TransactionsFixture
|
||||
import co.electriccoin.zcash.ui.screen.account.model.TransactionUiState
|
||||
import co.electriccoin.zcash.ui.screen.balances.model.StatusAction
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.widget.StyledExchangeOptIn
|
||||
import co.electriccoin.zcash.ui.util.PreviewScreens
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Preview("Account No History")
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun HistoryLoadingComposablePreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
Account(
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
balanceState =
|
||||
BalanceStateFixture.new(
|
||||
exchangeRate = ExchangeRateState.OptIn(onDismissClick = {})
|
||||
),
|
||||
isHideBalances = false,
|
||||
goBalances = {},
|
||||
goSettings = {},
|
||||
|
@ -64,10 +85,11 @@ private fun HistoryLoadingComposablePreview() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Composable
|
||||
@Preview("Account History List")
|
||||
@PreviewScreens
|
||||
private fun HistoryListComposablePreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
ZcashTheme {
|
||||
@Suppress("MagicNumber")
|
||||
Account(
|
||||
balanceState =
|
||||
|
@ -75,7 +97,7 @@ private fun HistoryListComposablePreview() {
|
|||
totalBalance = Zatoshi(value = 123_000_000L),
|
||||
spendableBalance = Zatoshi(value = 123_000_000L),
|
||||
exchangeRate =
|
||||
ExchangeRateState(
|
||||
ExchangeRateState.Data(
|
||||
isLoading = false,
|
||||
isRefreshEnabled = true,
|
||||
currencyConversion =
|
||||
|
@ -147,7 +169,8 @@ internal fun Account(
|
|||
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
// We intentionally do not set the bottom and horizontal paddings here. Those are set by the
|
||||
// underlying transaction history composable
|
||||
)
|
||||
),
|
||||
paddingValues = paddingValues
|
||||
)
|
||||
|
||||
// Show synchronization status popup
|
||||
|
@ -205,7 +228,7 @@ private fun AccountTopAppBar(
|
|||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "ModifierNotUsedAtRoot")
|
||||
private fun AccountMainContent(
|
||||
balanceState: BalanceState,
|
||||
goBalances: () -> Unit,
|
||||
|
@ -216,32 +239,72 @@ private fun AccountMainContent(
|
|||
transactionState: TransactionUiState,
|
||||
walletSnapshot: WalletSnapshot,
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues()
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
var delayedExchangeRateState by remember { mutableStateOf<ExchangeRateState?>(null) }
|
||||
|
||||
BalancesStatus(
|
||||
balanceState = balanceState,
|
||||
goBalances = goBalances,
|
||||
isHideBalances = isHideBalances,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(horizontal = ZcashTheme.dimens.screenHorizontalSpacingRegular),
|
||||
)
|
||||
LaunchedEffect(key1 = balanceState.exchangeRate) {
|
||||
if (delayedExchangeRateState == null && balanceState.exchangeRate is ExchangeRateState.OptIn) {
|
||||
delay(1.seconds)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
delayedExchangeRateState = balanceState.exchangeRate
|
||||
}
|
||||
|
||||
HistoryContainer(
|
||||
isHideBalances = isHideBalances,
|
||||
onStatusClick = onStatusClick,
|
||||
onTransactionItemAction = onTransactionItemAction,
|
||||
transactionState = transactionState,
|
||||
walletRestoringState = isWalletRestoringState,
|
||||
walletSnapshot = walletSnapshot,
|
||||
)
|
||||
Box {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
val bottomPadding =
|
||||
animateDpAsState(
|
||||
targetValue = if (delayedExchangeRateState is ExchangeRateState.OptIn) 76.dp else 0.dp,
|
||||
label = "bottom padding animation"
|
||||
)
|
||||
|
||||
BalancesStatus(
|
||||
balanceState = balanceState,
|
||||
goBalances = goBalances,
|
||||
isHideBalances = isHideBalances,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(
|
||||
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
||||
end = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
||||
bottom = bottomPadding.value
|
||||
),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
HistoryContainer(
|
||||
isHideBalances = isHideBalances,
|
||||
onStatusClick = onStatusClick,
|
||||
onTransactionItemAction = onTransactionItemAction,
|
||||
transactionState = transactionState,
|
||||
walletRestoringState = isWalletRestoringState,
|
||||
walletSnapshot = walletSnapshot,
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = delayedExchangeRateState is ExchangeRateState.OptIn,
|
||||
enter = fadeIn() + slideInVertically(),
|
||||
exit = fadeOut() + slideOutVertically(),
|
||||
) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(80.dp + paddingValues.calculateTopPadding()))
|
||||
StyledExchangeOptIn(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
state =
|
||||
(delayedExchangeRateState as? ExchangeRateState.OptIn) ?: ExchangeRateState.OptIn(
|
||||
onDismissClick = {},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
|||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.screen.advancedsettings.view.AdvancedSettings
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun MainActivity.WrapAdvancedSettings(
|
||||
goBack: () -> Unit,
|
||||
|
@ -18,6 +19,7 @@ internal fun MainActivity.WrapAdvancedSettings(
|
|||
goExportPrivateData: () -> Unit,
|
||||
goChooseServer: () -> Unit,
|
||||
goSeedRecovery: () -> Unit,
|
||||
onCurrencyConversion: () -> Unit
|
||||
) {
|
||||
val walletViewModel by viewModels<WalletViewModel>()
|
||||
|
||||
|
@ -29,7 +31,8 @@ internal fun MainActivity.WrapAdvancedSettings(
|
|||
goExportPrivateData = goExportPrivateData,
|
||||
goChooseServer = goChooseServer,
|
||||
goSeedRecovery = goSeedRecovery,
|
||||
topAppBarSubTitleState = walletState
|
||||
topAppBarSubTitleState = walletState,
|
||||
onCurrencyConversion = onCurrencyConversion
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -41,6 +44,7 @@ private fun WrapAdvancedSettings(
|
|||
goChooseServer: () -> Unit,
|
||||
goSeedRecovery: () -> Unit,
|
||||
goDeleteWallet: () -> Unit,
|
||||
onCurrencyConversion: () -> Unit,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
) {
|
||||
BackHandler {
|
||||
|
@ -54,5 +58,6 @@ private fun WrapAdvancedSettings(
|
|||
onChooseServer = goChooseServer,
|
||||
onSeedRecovery = goSeedRecovery,
|
||||
topAppBarSubTitleState = topAppBarSubTitleState,
|
||||
onCurrencyConversion = onCurrencyConversion
|
||||
)
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@ private fun PreviewAdvancedSettings() {
|
|||
onExportPrivateData = {},
|
||||
onChooseServer = {},
|
||||
onSeedRecovery = {},
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
onCurrencyConversion = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +55,7 @@ fun AdvancedSettings(
|
|||
onExportPrivateData: () -> Unit,
|
||||
onChooseServer: () -> Unit,
|
||||
onSeedRecovery: () -> Unit,
|
||||
onCurrencyConversion: () -> Unit,
|
||||
topAppBarSubTitleState: TopAppBarSubTitleState,
|
||||
) {
|
||||
BlankBgScaffold(
|
||||
|
@ -80,6 +82,7 @@ fun AdvancedSettings(
|
|||
onExportPrivateData = onExportPrivateData,
|
||||
onSeedRecovery = onSeedRecovery,
|
||||
onChooseServer = onChooseServer,
|
||||
onCurrencyConversion = onCurrencyConversion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -108,11 +111,13 @@ private fun AdvancedSettingsTopAppBar(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun AdvancedSettingsMainContent(
|
||||
onDeleteWallet: () -> Unit,
|
||||
onExportPrivateData: () -> Unit,
|
||||
onChooseServer: () -> Unit,
|
||||
onCurrencyConversion: () -> Unit,
|
||||
onSeedRecovery: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
@ -144,6 +149,14 @@ private fun AdvancedSettingsMainContent(
|
|||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(dimens.spacingDefault))
|
||||
|
||||
PrimaryButton(
|
||||
onClick = onCurrencyConversion,
|
||||
text = stringResource(R.string.advanced_settings_currency_conversion),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun BaseExchangeRateOptIn(
|
||||
onDismiss: () -> Unit,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
footer: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Scaffold {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
start = 24.dp,
|
||||
top = it.calculateTopPadding() + 12.dp,
|
||||
end = 24.dp,
|
||||
bottom = it.calculateBottomPadding() + 24.dp
|
||||
)
|
||||
) {
|
||||
Button(
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
modifier = Modifier.size(40.dp),
|
||||
onClick = onDismiss,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = ZcashTheme.zashiColors.btnPrimaryBgDisabled
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_exchange_rate_close),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(ZcashTheme.zashiColors.btnTertiaryFg)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Image(painter = painterResource(Image), contentDescription = "")
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "Currency Conversion",
|
||||
color = ZcashTheme.zashiColors.textPrimary,
|
||||
fontSize = 24.sp,
|
||||
style = ZcashTheme.extendedTypography.restoringTopAppBarStyle,
|
||||
)
|
||||
content()
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Row {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_exchange_rate_info),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(ZcashTheme.zashiColors.textPrimary)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.exchange_rate_opt_in_note),
|
||||
color = ZcashTheme.zashiColors.textTertiary,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
footer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Image: Int
|
||||
@DrawableRes
|
||||
@Composable
|
||||
get() =
|
||||
if (isSystemInDarkTheme()) {
|
||||
R.drawable.exchange_rate
|
||||
} else {
|
||||
R.drawable.exchange_rate_light
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
||||
@Composable
|
||||
internal fun SecondaryCard(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
elevation = CardDefaults.elevatedCardElevation(0.dp),
|
||||
border = BorderStroke(1.dp, ZcashTheme.zashiColors.strokeSecondary),
|
||||
colors =
|
||||
CardDefaults.cardColors(
|
||||
containerColor = ZcashTheme.zashiColors.bgPrimary
|
||||
),
|
||||
content = content
|
||||
)
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
||||
@Composable
|
||||
internal fun ZashiButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
colors: ButtonColors = ZashiButtonDefaults.primaryButtonColors(),
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
enabled = enabled,
|
||||
colors = colors,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
object ZashiButtonDefaults {
|
||||
@Composable
|
||||
fun primaryButtonColors(
|
||||
containerColor: Color = ZcashTheme.zashiColors.btnPrimaryBg,
|
||||
contentColor: Color = ZcashTheme.zashiColors.btnPrimaryFg,
|
||||
disabledContainerColor: Color = ZcashTheme.zashiColors.btnPrimaryBgDisabled,
|
||||
disabledContentColor: Color = ZcashTheme.zashiColors.btnPrimaryFgDisabled,
|
||||
): ButtonColors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
disabledContainerColor = disabledContainerColor,
|
||||
disabledContentColor = disabledContentColor
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun tertiaryButtonColors(
|
||||
containerColor: Color = ZcashTheme.zashiColors.btnTertiaryBg,
|
||||
contentColor: Color = ZcashTheme.zashiColors.btnTertiaryFg,
|
||||
disabledContainerColor: Color = Color.Unspecified,
|
||||
disabledContentColor: Color = Color.Unspecified,
|
||||
): ButtonColors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
disabledContainerColor = disabledContainerColor,
|
||||
disabledContentColor = disabledContentColor
|
||||
)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
||||
@Composable
|
||||
internal fun ZashiTextButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
colors: ButtonColors = ZashiTextButtonDefaults.textButtonColors(),
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
enabled = enabled,
|
||||
colors = colors,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
object ZashiTextButtonDefaults {
|
||||
@Composable
|
||||
fun textButtonColors(
|
||||
containerColor: Color = Color.Unspecified,
|
||||
contentColor: Color = ZcashTheme.zashiColors.btnTextFg,
|
||||
disabledContainerColor: Color = Color.Unspecified,
|
||||
disabledContentColor: Color = Color.Unspecified,
|
||||
): ButtonColors =
|
||||
ButtonDefaults.textButtonColors(
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
disabledContainerColor = disabledContainerColor,
|
||||
disabledContentColor = disabledContentColor
|
||||
)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate.optin
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalActivity
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
|
||||
@Composable
|
||||
fun AndroidExchangeRateOptIn() {
|
||||
val activity = LocalActivity.current
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
BackHandler {
|
||||
walletViewModel.dismissOptInExchangeRateUsd()
|
||||
}
|
||||
|
||||
ExchangeRateOptIn(
|
||||
onEnabledClick = { walletViewModel.optInExchangeRateUsd(true) },
|
||||
onDismiss = { walletViewModel.dismissOptInExchangeRateUsd() }
|
||||
)
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate.optin
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.BaseExchangeRateOptIn
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.ZashiButton
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.ZashiButtonDefaults
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.ZashiTextButton
|
||||
import co.electriccoin.zcash.ui.util.PreviewScreens
|
||||
|
||||
@Composable
|
||||
fun ExchangeRateOptIn(
|
||||
onEnabledClick: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
BaseExchangeRateOptIn(
|
||||
onDismiss = onDismiss,
|
||||
content = {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.exchange_rate_opt_in_description),
|
||||
color = ZcashTheme.zashiColors.textTertiary,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
InfoItem(
|
||||
modifier = Modifier,
|
||||
image = R.drawable.ic_exchange_rate_info_1,
|
||||
title = stringResource(R.string.exchange_rate_info_title_1),
|
||||
subtitle = stringResource(R.string.exchange_rate_info_subtitle_1),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
InfoItem(
|
||||
modifier = Modifier,
|
||||
image = R.drawable.ic_exchange_rate_info_2,
|
||||
title = stringResource(R.string.exchange_rate_info_title_2),
|
||||
subtitle = stringResource(R.string.exchange_rate_info_subtitle_2),
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
ZashiButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onEnabledClick,
|
||||
colors = ZashiButtonDefaults.primaryButtonColors()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.exchange_rate_opt_in_enable)
|
||||
)
|
||||
}
|
||||
ZashiTextButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onDismiss,
|
||||
) {
|
||||
Text(text = stringResource(R.string.exchange_rate_opt_in_skip))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoItem(
|
||||
@DrawableRes image: Int,
|
||||
title: String,
|
||||
subtitle: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(modifier) {
|
||||
Image(
|
||||
painter = painterResource(image),
|
||||
contentDescription = ""
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
color = ZcashTheme.zashiColors.textPrimary,
|
||||
fontSize = 16.sp,
|
||||
style = ZcashTheme.extendedTypography.restoringTopAppBarStyle,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = ZcashTheme.zashiColors.textTertiary,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun CurrencyConversionOptInPreview() =
|
||||
ZcashTheme {
|
||||
BlankSurface {
|
||||
ExchangeRateOptIn(onEnabledClick = {}, onDismiss = {})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate.settings
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalActivity
|
||||
import co.electriccoin.zcash.ui.common.compose.LocalNavController
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
|
||||
@Composable
|
||||
fun AndroidSettingsExchangeRateOptIn() {
|
||||
val activity = LocalActivity.current
|
||||
val navController = LocalNavController.current
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
val isOptedIn = walletViewModel.isExchangeRateUsdOptedIn.collectAsStateWithLifecycle().value ?: false
|
||||
|
||||
BackHandler {
|
||||
navController.popBackStack()
|
||||
}
|
||||
|
||||
SettingsExchangeRateOptIn(
|
||||
isOptedIn = isOptedIn,
|
||||
onSaveClick = { walletViewModel.optInExchangeRateUsd(it) },
|
||||
onDismiss = { navController.popBackStack() }
|
||||
)
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate.settings
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.BaseExchangeRateOptIn
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.SecondaryCard
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.ZashiButton
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.ZashiButtonDefaults
|
||||
import co.electriccoin.zcash.ui.util.PreviewScreens
|
||||
|
||||
@Composable
|
||||
fun SettingsExchangeRateOptIn(
|
||||
isOptedIn: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onSaveClick: (Boolean) -> Unit
|
||||
) {
|
||||
var isOptInSelected by remember(isOptedIn) { mutableStateOf(isOptedIn) }
|
||||
|
||||
val isButtonDisabled by remember {
|
||||
derivedStateOf {
|
||||
(isOptedIn && isOptInSelected) || (!isOptedIn && !isOptInSelected)
|
||||
}
|
||||
}
|
||||
|
||||
BaseExchangeRateOptIn(
|
||||
onDismiss = onDismiss,
|
||||
content = {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.exchange_rate_opt_in_description_settings),
|
||||
color = ZcashTheme.zashiColors.textTertiary,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Option(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
image = OptIn,
|
||||
selectionImage = if (isOptInSelected) Checked else Unchecked,
|
||||
title = stringResource(R.string.exchange_rate_opt_in_option_title),
|
||||
subtitle = stringResource(R.string.exchange_rate_opt_in_option_subtitle),
|
||||
onClick = { isOptInSelected = true }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Option(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
image = OptOut,
|
||||
selectionImage = if (!isOptInSelected) Checked else Unchecked,
|
||||
title = stringResource(R.string.exchange_rate_opt_out_option_title),
|
||||
subtitle = stringResource(R.string.exchange_rate_opt_out_option_subtitle),
|
||||
onClick = { isOptInSelected = false }
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
ZashiButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { onSaveClick(isOptInSelected) },
|
||||
enabled = !isButtonDisabled,
|
||||
colors = ZashiButtonDefaults.primaryButtonColors()
|
||||
) {
|
||||
Text(text = stringResource(R.string.exchange_rate_opt_in_save))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun Option(
|
||||
@DrawableRes image: Int,
|
||||
@DrawableRes selectionImage: Int,
|
||||
title: String,
|
||||
subtitle: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SecondaryCard(
|
||||
modifier =
|
||||
modifier.clickable(
|
||||
onClick = onClick,
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
Modifier.padding(20.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(image),
|
||||
contentDescription = ""
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
color = ZcashTheme.zashiColors.textPrimary,
|
||||
fontSize = 16.sp,
|
||||
style = ZcashTheme.extendedTypography.restoringTopAppBarStyle,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = subtitle,
|
||||
color = ZcashTheme.zashiColors.textTertiary,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Image(
|
||||
painter = painterResource(selectionImage),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val OptIn: Int
|
||||
@DrawableRes
|
||||
@Composable
|
||||
get() =
|
||||
if (isSystemInDarkTheme()) {
|
||||
R.drawable.ic_opt_in
|
||||
} else {
|
||||
R.drawable.ic_opt_in_light
|
||||
}
|
||||
|
||||
private val OptOut: Int
|
||||
@DrawableRes
|
||||
@Composable
|
||||
get() =
|
||||
if (isSystemInDarkTheme()) {
|
||||
R.drawable.ic_opt_out
|
||||
} else {
|
||||
R.drawable.ic_opt_out_light
|
||||
}
|
||||
|
||||
private val Checked: Int
|
||||
@DrawableRes
|
||||
@Composable
|
||||
get() =
|
||||
if (isSystemInDarkTheme()) {
|
||||
R.drawable.ic_checkbox_checked
|
||||
} else {
|
||||
R.drawable.ic_checkbox_checked_light
|
||||
}
|
||||
|
||||
private val Unchecked: Int
|
||||
@DrawableRes
|
||||
@Composable
|
||||
get() =
|
||||
if (isSystemInDarkTheme()) {
|
||||
R.drawable.ic_checkbox_unchecked
|
||||
} else {
|
||||
R.drawable.ic_checkbox_unchecked_light
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun SettingsExchangeRateOptInPreview() =
|
||||
ZcashTheme {
|
||||
BlankSurface {
|
||||
SettingsExchangeRateOptIn(isOptedIn = true, onDismiss = {}, onSaveClick = {})
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package co.electriccoin.zcash.ui
|
||||
package co.electriccoin.zcash.ui.screen.exchangerate.widget
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
|
@ -39,6 +39,7 @@ import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
|
|||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import cash.z.ecc.android.sdk.model.toFiatString
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.extension.toKotlinLocale
|
||||
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
|
@ -67,56 +68,47 @@ fun StyledExchangeBalance(
|
|||
textColor: Color = ZcashTheme.exchangeRateColors.btnSecondaryFg,
|
||||
style: TextStyle = ZcashTheme.typography.primary.titleSmall.copy(fontWeight = FontWeight.SemiBold)
|
||||
) {
|
||||
if ((state.isStale && !state.isLoading) ||
|
||||
(!state.isLoading && state.currencyConversion == null)
|
||||
) {
|
||||
ExchangeRateUnavailableButton(
|
||||
textColor = textColor,
|
||||
style = style,
|
||||
modifier = modifier
|
||||
)
|
||||
} else {
|
||||
ExchangeAvailableRateButton(
|
||||
style = style,
|
||||
textColor = textColor,
|
||||
zatoshi = zatoshi,
|
||||
isHideBalances = isHideBalances,
|
||||
state = state,
|
||||
hiddenBalancePlaceholder = hiddenBalancePlaceholder
|
||||
)
|
||||
when (state) {
|
||||
is ExchangeRateState.Data ->
|
||||
if ((state.isStale && !state.isLoading) || (!state.isLoading && state.currencyConversion == null)) {
|
||||
ExchangeRateUnavailableButton(
|
||||
textColor = textColor,
|
||||
style = style,
|
||||
modifier = modifier
|
||||
)
|
||||
} else {
|
||||
ExchangeAvailableRateLabelInternal(
|
||||
style = style,
|
||||
textColor = textColor,
|
||||
zatoshi = zatoshi,
|
||||
isHideBalances = isHideBalances,
|
||||
state = state,
|
||||
hiddenBalancePlaceholder = hiddenBalancePlaceholder
|
||||
)
|
||||
}
|
||||
|
||||
is ExchangeRateState.OptIn -> {
|
||||
// do not show anything
|
||||
}
|
||||
|
||||
ExchangeRateState.OptedOut -> {
|
||||
// do not show anything
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun ExchangeAvailableRateButton(
|
||||
private fun ExchangeAvailableRateLabelInternal(
|
||||
style: TextStyle,
|
||||
textColor: Color,
|
||||
zatoshi: Zatoshi,
|
||||
isHideBalances: Boolean,
|
||||
state: ExchangeRateState,
|
||||
state: ExchangeRateState.Data,
|
||||
hiddenBalancePlaceholder: StringResource,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val currencySymbol = state.fiatCurrency.symbol
|
||||
val text =
|
||||
if (isHideBalances) {
|
||||
"${currencySymbol}${hiddenBalancePlaceholder.getValue()}"
|
||||
} else if (state.currencyConversion != null) {
|
||||
val value =
|
||||
zatoshi.toFiatString(
|
||||
currencyConversion = state.currencyConversion,
|
||||
locale = Locale.current.toKotlinLocale(),
|
||||
monetarySeparators = MonetarySeparators.current(java.util.Locale.getDefault()),
|
||||
includeSymbols = false
|
||||
)
|
||||
|
||||
"$currencySymbol$value"
|
||||
} else {
|
||||
currencySymbol
|
||||
}
|
||||
|
||||
val isEnabled = !state.isLoading && state.isRefreshEnabled
|
||||
|
||||
ExchangeRateButton(
|
||||
|
@ -129,7 +121,7 @@ private fun ExchangeAvailableRateButton(
|
|||
textColor = textColor,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
text = createExchangeRateText(state, isHideBalances, hiddenBalancePlaceholder, zatoshi),
|
||||
style = style,
|
||||
maxLines = 1,
|
||||
color = textColor
|
||||
|
@ -177,6 +169,33 @@ private fun ExchangeAvailableRateButton(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun createExchangeRateText(
|
||||
state: ExchangeRateState.Data,
|
||||
isHideBalances: Boolean,
|
||||
hiddenBalancePlaceholder: StringResource,
|
||||
zatoshi: Zatoshi
|
||||
): String {
|
||||
val currencySymbol = state.fiatCurrency.symbol
|
||||
val text =
|
||||
if (isHideBalances) {
|
||||
"${currencySymbol}${hiddenBalancePlaceholder.getValue()}"
|
||||
} else if (state.currencyConversion != null) {
|
||||
val value =
|
||||
zatoshi.toFiatString(
|
||||
currencyConversion = state.currencyConversion,
|
||||
locale = Locale.current.toKotlinLocale(),
|
||||
monetarySeparators = MonetarySeparators.current(java.util.Locale.getDefault()),
|
||||
includeSymbols = false
|
||||
)
|
||||
|
||||
"$currencySymbol$value"
|
||||
} else {
|
||||
currencySymbol
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExchangeRateUnavailableButton(
|
||||
textColor: Color,
|
||||
|
@ -199,7 +218,7 @@ private fun ExchangeRateUnavailableButton(
|
|||
textColor = textColor,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.balances_exchange_rate_unavailable),
|
||||
text = stringResource(id = R.string.exchange_rate_unavailable_title),
|
||||
style = style,
|
||||
maxLines = 1,
|
||||
color = textColor
|
||||
|
@ -211,7 +230,7 @@ private fun ExchangeRateUnavailableButton(
|
|||
modifier =
|
||||
Modifier
|
||||
.align(CenterVertically),
|
||||
painter = painterResource(R.drawable.ic_unavailable_exchange_rate),
|
||||
painter = painterResource(R.drawable.ic_exchange_rate_info),
|
||||
contentDescription = "",
|
||||
colorFilter = ColorFilter.tint(textColor)
|
||||
)
|
||||
|
@ -219,7 +238,7 @@ private fun ExchangeRateUnavailableButton(
|
|||
|
||||
if (transitionState.currentState || transitionState.targetState || !transitionState.isIdle) {
|
||||
val offset = with(LocalDensity.current) { 78.dp.toPx() }.toInt()
|
||||
UnavailableExchangeRatePopup(
|
||||
StyledExchangeUnavailablePopup(
|
||||
onDismissRequest = {
|
||||
transitionState.targetState = false
|
||||
},
|
||||
|
@ -287,7 +306,7 @@ private fun DefaultPreview() =
|
|||
modifier = Modifier,
|
||||
zatoshi = Zatoshi(1),
|
||||
state =
|
||||
ExchangeRateState(
|
||||
ExchangeRateState.Data(
|
||||
isLoading = false,
|
||||
currencyConversion =
|
||||
FiatCurrencyConversion(
|
||||
|
@ -313,7 +332,7 @@ private fun DefaultNoRefreshPreview() =
|
|||
modifier = Modifier,
|
||||
zatoshi = Zatoshi(1),
|
||||
state =
|
||||
ExchangeRateState(
|
||||
ExchangeRateState.Data(
|
||||
isLoading = false,
|
||||
currencyConversion =
|
||||
FiatCurrencyConversion(
|
||||
|
@ -340,7 +359,7 @@ private fun HiddenPreview() =
|
|||
modifier = Modifier,
|
||||
zatoshi = Zatoshi(1),
|
||||
state =
|
||||
ExchangeRateState(
|
||||
ExchangeRateState.Data(
|
||||
isLoading = false,
|
||||
currencyConversion =
|
||||
FiatCurrencyConversion(
|
||||
|
@ -366,7 +385,7 @@ private fun HiddenStalePreview() =
|
|||
modifier = Modifier,
|
||||
zatoshi = Zatoshi(1),
|
||||
state =
|
||||
ExchangeRateState(
|
||||
ExchangeRateState.Data(
|
||||
isLoading = false,
|
||||
isStale = true,
|
||||
currencyConversion =
|
|
@ -0,0 +1,75 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate.widget
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversion
|
||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.util.PreviewScreens
|
||||
import co.electriccoin.zcash.ui.util.StringResource
|
||||
import co.electriccoin.zcash.ui.util.stringRes
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@Suppress("LongParameterList", "ComplexCondition")
|
||||
@Composable
|
||||
fun StyledExchangeLabel(
|
||||
zatoshi: Zatoshi,
|
||||
state: ExchangeRateState,
|
||||
modifier: Modifier = Modifier,
|
||||
isHideBalances: Boolean = false,
|
||||
hiddenBalancePlaceholder: StringResource =
|
||||
stringRes(co.electriccoin.zcash.ui.design.R.string.hide_balance_placeholder),
|
||||
style: TextStyle = ZcashTheme.typography.secondary.headlineSmall,
|
||||
textColor: Color = ZcashTheme.colors.textFieldHint,
|
||||
) {
|
||||
when (state) {
|
||||
is ExchangeRateState.Data ->
|
||||
if (!state.isStale && state.currencyConversion != null) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = createExchangeRateText(state, isHideBalances, hiddenBalancePlaceholder, zatoshi),
|
||||
maxLines = 1,
|
||||
color = textColor,
|
||||
style = style,
|
||||
)
|
||||
}
|
||||
|
||||
is ExchangeRateState.OptIn -> {
|
||||
// do not show anything
|
||||
}
|
||||
|
||||
ExchangeRateState.OptedOut -> {
|
||||
// do not show anything
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@PreviewScreens
|
||||
@Composable
|
||||
private fun DefaultPreview() =
|
||||
ZcashTheme {
|
||||
BlankSurface {
|
||||
StyledExchangeLabel(
|
||||
isHideBalances = false,
|
||||
modifier = Modifier,
|
||||
zatoshi = Zatoshi(1),
|
||||
state =
|
||||
ExchangeRateState.Data(
|
||||
isLoading = false,
|
||||
isStale = false,
|
||||
currencyConversion =
|
||||
FiatCurrencyConversion(
|
||||
timestamp = Clock.System.now(),
|
||||
priceOfZec = 25.0
|
||||
),
|
||||
onRefresh = {}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package co.electriccoin.zcash.ui.screen.exchangerate.widget
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.wallet.ExchangeRateState
|
||||
import co.electriccoin.zcash.ui.design.component.BlankSurface
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.SecondaryCard
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.ZashiButton
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.ZashiButtonDefaults
|
||||
import co.electriccoin.zcash.ui.util.PreviewScreens
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun StyledExchangeOptIn(
|
||||
state: ExchangeRateState.OptIn,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
SecondaryCard(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 20.dp, bottom = 20.dp)
|
||||
) {
|
||||
Row {
|
||||
Image(
|
||||
modifier = Modifier.padding(top = 20.dp),
|
||||
painter = painterResource(Icon),
|
||||
contentDescription = ""
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(22.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.exchange_rate_opt_in_title),
|
||||
color = ZcashTheme.zashiColors.textTertiary,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.exchange_rate_opt_in_subtitle),
|
||||
color = ZcashTheme.zashiColors.textPrimary,
|
||||
fontSize = 16.sp,
|
||||
style = ZcashTheme.extendedTypography.restoringTopAppBarStyle,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
IconButton(
|
||||
modifier = Modifier.padding(top = 4.dp, end = 8.dp),
|
||||
onClick = state.onDismissClick,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_exchange_rate_unavailable_dialog_close),
|
||||
contentDescription = "",
|
||||
tint = ZcashTheme.zashiColors.defaultFg
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ZashiButton(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 20.dp),
|
||||
onClick = state.onPrimaryClick,
|
||||
colors = ZashiButtonDefaults.tertiaryButtonColors()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.exchange_rate_opt_in_primary_btn),
|
||||
style = ZcashTheme.typography.primary.titleSmall.copy(fontWeight = FontWeight.SemiBold),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Icon: Int
|
||||
@DrawableRes
|
||||
@Composable
|
||||
get() =
|
||||
if (isSystemInDarkTheme()) {
|
||||
R.drawable.ic_exchange_rate_opt_in
|
||||
} else {
|
||||
R.drawable.ic_exchange_rate_opt_in_light
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Composable
|
||||
@PreviewScreens
|
||||
private fun ExchangeRateOptInPreview() =
|
||||
ZcashTheme {
|
||||
BlankSurface {
|
||||
StyledExchangeOptIn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = ExchangeRateState.OptIn(onDismissClick = {})
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package co.electriccoin.zcash.ui
|
||||
package co.electriccoin.zcash.ui.screen.exchangerate.widget
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.MutableTransitionState
|
||||
|
@ -34,11 +34,12 @@ import androidx.compose.ui.unit.IntOffset
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.util.PreviewScreens
|
||||
|
||||
@Composable
|
||||
internal fun UnavailableExchangeRatePopup(
|
||||
internal fun StyledExchangeUnavailablePopup(
|
||||
offset: IntOffset,
|
||||
transitionState: MutableTransitionState<Boolean>,
|
||||
onDismissRequest: () -> Unit,
|
||||
|
@ -93,7 +94,7 @@ private fun PopupContent(onDismissRequest: () -> Unit) {
|
|||
color = ZcashTheme.zashiColors.textLight,
|
||||
fontSize = 16.sp,
|
||||
style = ZcashTheme.extendedTypography.restoringTopAppBarStyle,
|
||||
text = stringResource(R.string.balances_exchange_rate_unavailable)
|
||||
text = stringResource(R.string.exchange_rate_unavailable_title)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
|
@ -103,12 +104,12 @@ private fun PopupContent(onDismissRequest: () -> Unit) {
|
|||
fontWeight = FontWeight.Medium
|
||||
),
|
||||
fontSize = 14.sp,
|
||||
text = stringResource(id = R.string.balances_exchange_rate_unavailable_subtitle)
|
||||
text = stringResource(id = R.string.exchange_rate_unavailable_subtitle)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onDismissRequest) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_unavailable_exchange_rate_dialog_close),
|
||||
painter = painterResource(R.drawable.ic_exchange_rate_unavailable_dialog_close),
|
||||
contentDescription = "",
|
||||
tint = ZcashTheme.zashiColors.textLightSupport
|
||||
)
|
|
@ -45,7 +45,9 @@ sealed interface AmountState {
|
|||
val zatoshi = Zatoshi.fromZecString(context, value, monetarySeparators)
|
||||
|
||||
val currencyConversion =
|
||||
if (!exchangeRateState.isLoading && exchangeRateState.isStale) {
|
||||
if (exchangeRateState !is ExchangeRateState.Data ||
|
||||
(!exchangeRateState.isLoading && exchangeRateState.isStale)
|
||||
) {
|
||||
null
|
||||
} else {
|
||||
exchangeRateState.currencyConversion
|
||||
|
@ -91,7 +93,7 @@ sealed interface AmountState {
|
|||
}
|
||||
|
||||
val zatoshi =
|
||||
exchangeRateState.currencyConversion?.toZatoshi(
|
||||
(exchangeRateState as? ExchangeRateState.Data)?.currencyConversion?.toZatoshi(
|
||||
context = context,
|
||||
value = fiatValue,
|
||||
monetarySeparators = MonetarySeparators.current(java.util.Locale.getDefault())
|
||||
|
|
|
@ -122,7 +122,7 @@ private fun PreviewSendForm() {
|
|||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
isHideBalances = false,
|
||||
exchangeRateState = ExchangeRateState {}
|
||||
exchangeRateState = ExchangeRateState.OptedOut
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ private fun SendFormTransparentAddressPreview() {
|
|||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
balanceState = BalanceStateFixture.new(),
|
||||
isHideBalances = false,
|
||||
exchangeRateState = ExchangeRateState {}
|
||||
exchangeRateState = ExchangeRateState.OptedOut
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -708,7 +708,9 @@ fun SendFormAmountTextField(
|
|||
}
|
||||
)
|
||||
|
||||
if (!exchangeRateState.isStale || exchangeRateState.isLoading) {
|
||||
if (exchangeRateState is ExchangeRateState.Data &&
|
||||
(!exchangeRateState.isStale || exchangeRateState.isLoading)
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingMin))
|
||||
Image(
|
||||
modifier = Modifier.padding(top = 24.dp),
|
||||
|
|
|
@ -75,7 +75,7 @@ internal fun MainActivity.WrapSendConfirmation(
|
|||
|
||||
val walletState = walletViewModel.walletStateInformation.collectAsStateWithLifecycle().value
|
||||
|
||||
val exchangeRateState = walletViewModel.exchangeRateUsd.collectAsStateWithLifecycle().value
|
||||
val exchangeRateState by remember { mutableStateOf(walletViewModel.exchangeRateUsd.value) }
|
||||
|
||||
WrapSendConfirmation(
|
||||
activity = this,
|
||||
|
@ -133,9 +133,13 @@ internal fun WrapSendConfirmation(
|
|||
val onBackAction = {
|
||||
when (stage) {
|
||||
SendConfirmationStage.Confirmation -> goBack(false)
|
||||
SendConfirmationStage.Sending -> { /* no action - wait until the sending is done */ }
|
||||
SendConfirmationStage.Sending -> { // no action - wait until the sending is done
|
||||
}
|
||||
|
||||
is SendConfirmationStage.Failure -> setStage(SendConfirmationStage.Confirmation)
|
||||
is SendConfirmationStage.MultipleTrxFailure -> { /* no action - wait until report the result */ }
|
||||
is SendConfirmationStage.MultipleTrxFailure -> { // no action - wait until report the result
|
||||
}
|
||||
|
||||
is SendConfirmationStage.MultipleTrxFailureReported -> goBack(true)
|
||||
}
|
||||
}
|
||||
|
@ -311,9 +315,11 @@ private fun processSubmissionResult(
|
|||
setStage(SendConfirmationStage.Confirmation)
|
||||
goHome()
|
||||
}
|
||||
|
||||
is SubmitResult.SimpleTrxFailure -> {
|
||||
setStage(SendConfirmationStage.Failure(submitResult.errorDescription))
|
||||
}
|
||||
|
||||
is SubmitResult.MultipleTrxFailure -> {
|
||||
setStage(SendConfirmationStage.MultipleTrxFailure)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ import cash.z.ecc.sdk.extension.toZecStringFull
|
|||
import cash.z.ecc.sdk.fixture.MemoFixture
|
||||
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.StyledExchangeBalance
|
||||
import co.electriccoin.zcash.ui.common.compose.BalanceWidgetBigLineOnly
|
||||
import co.electriccoin.zcash.ui.common.extension.asZecAmountTriple
|
||||
import co.electriccoin.zcash.ui.common.model.TopAppBarSubTitleState
|
||||
|
@ -61,6 +60,7 @@ import co.electriccoin.zcash.ui.design.component.Tiny
|
|||
import co.electriccoin.zcash.ui.design.component.TopAppBarBackNavigation
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.ObserveFiatCurrencyResultFixture
|
||||
import co.electriccoin.zcash.ui.screen.exchangerate.widget.StyledExchangeLabel
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.SendConfirmationTag
|
||||
import co.electriccoin.zcash.ui.screen.sendconfirmation.model.SendConfirmationStage
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
@ -393,12 +393,10 @@ private fun SendConfirmationContent(
|
|||
isHideBalances = false
|
||||
)
|
||||
|
||||
StyledExchangeBalance(
|
||||
StyledExchangeLabel(
|
||||
zatoshi = zecSend.amount,
|
||||
state = exchangeRate,
|
||||
isHideBalances = false,
|
||||
style = ZcashTheme.typography.secondary.headlineSmall,
|
||||
textColor = ZcashTheme.colors.textFieldHint
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<string name="advanced_settings_backup_wallet">Recovery phrase</string>
|
||||
<string name="advanced_settings_export_private_data">Export private data</string>
|
||||
<string name="advanced_settings_choose_server">Choose a server</string>
|
||||
<string name="advanced_settings_currency_conversion">Currency Conversion</string>
|
||||
|
||||
<string name="advanced_settings_delete_wallet">
|
||||
Delete <xliff:g id="app_name" example="Zashi">%1$s</xliff:g>
|
||||
|
|
|
@ -48,7 +48,4 @@
|
|||
<string name="balances_shielding_dialog_error_text">Error: The attempt to shield the transparent funds failed. Try it again, please.</string>
|
||||
<string name="balances_shielding_dialog_error_btn">OK</string>
|
||||
<string name="balances_shielding_dialog_error_below_threshold">The current transparent balance is zero or below the allowed shielding limit.</string>
|
||||
<string name="balances_exchange_rate_unavailable">Exchange rate unavailable</string>
|
||||
<string name="balances_exchange_rate_unavailable_subtitle">We tried but we couldn\’t refresh the exchange rate
|
||||
for you. Check your connection, relaunch the app, and we’ll try again.</string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="192dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="72">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M4,36C4,18.33 18.33,4 36,4C53.67,4 68,18.33 68,36C68,53.67 53.67,68 36,68C18.33,68 4,53.67 4,36Z"/>
|
||||
<path
|
||||
android:pathData="M36,4L36,4A32,32 0,0 1,68 36L68,36A32,32 0,0 1,36 68L36,68A32,32 0,0 1,4 36L4,36A32,32 0,0 1,36 4z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M4,36C4,18.35 18.35,4 36,4C53.65,4 68,18.35 68,36C68,53.65 53.65,68 36,68C18.35,68 4,53.65 4,36ZM47.41,21.15V26.02L33.87,44.39H47.41V50.85H38.68V56.2H33.32V50.85H24.59V45.98L38.12,27.61H24.59V21.15H33.32V15.78H38.68V21.15H47.41Z"
|
||||
android:fillColor="#FCBB1A"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
<path
|
||||
android:pathData="M36,2C17.22,2 2,17.22 2,36C2,54.78 17.22,70 36,70C54.78,70 70,54.78 70,36C70,17.22 54.78,2 36,2Z"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M96,2C77.22,2 62,17.22 62,36C62,54.78 77.22,70 96,70C114.78,70 130,54.78 130,36C130,17.22 114.78,2 96,2Z"
|
||||
android:fillColor="#3B3839"/>
|
||||
<path
|
||||
android:pathData="M96,2C77.22,2 62,17.22 62,36C62,54.78 77.22,70 96,70C114.78,70 130,54.78 130,36C130,17.22 114.78,2 96,2Z"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M106.67,42.67H85.33M85.33,42.67L90.67,37.33M85.33,42.67L90.67,48M85.33,29.33H106.67M106.67,29.33L101.33,24M106.67,29.33L101.33,34.67"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M156,2C137.22,2 122,17.22 122,36C122,54.78 137.22,70 156,70C174.78,70 190,54.78 190,36C190,17.22 174.78,2 156,2Z"
|
||||
android:fillColor="#067647"/>
|
||||
<path
|
||||
android:pathData="M156,2C137.22,2 122,17.22 122,36C122,54.78 137.22,70 156,70C174.78,70 190,54.78 190,36C190,17.22 174.78,2 156,2Z"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M148,41.33C148,44.28 150.39,46.67 153.33,46.67H158.67C161.61,46.67 164,44.28 164,41.33C164,38.39 161.61,36 158.67,36H153.33C150.39,36 148,33.61 148,30.67C148,27.72 150.39,25.33 153.33,25.33H158.67C161.61,25.33 164,27.72 164,30.67M156,22.67V49.33"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ABEFC6"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,52 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="204dp"
|
||||
android:height="83dp"
|
||||
android:viewportWidth="204"
|
||||
android:viewportHeight="83">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M10,39C10,21.33 24.33,7 42,7C59.67,7 74,21.33 74,39C74,56.67 59.67,71 42,71C24.33,71 10,56.67 10,39Z"/>
|
||||
<path
|
||||
android:pathData="M42,7L42,7A32,32 0,0 1,74 39L74,39A32,32 0,0 1,42 71L42,71A32,32 0,0 1,10 39L10,39A32,32 0,0 1,42 7z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M10,39C10,21.35 24.35,7 42,7C59.65,7 74,21.35 74,39C74,56.65 59.65,71 42,71C24.35,71 10,56.65 10,39ZM53.41,24.15V29.02L39.87,47.39H53.41V53.85H44.68V59.2H39.32V53.85H30.58V48.98L44.12,30.61H30.58V24.15H39.32V18.78H44.68V24.15H53.41Z"
|
||||
android:fillColor="#FCBB1A"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
<path
|
||||
android:pathData="M42,5C23.22,5 8,20.22 8,39C8,57.78 23.22,73 42,73C60.78,73 76,57.78 76,39C76,20.22 60.78,5 42,5Z"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M70,39C70,21.33 84.33,7 102,7C119.67,7 134,21.33 134,39C134,56.67 119.67,71 102,71C84.33,71 70,56.67 70,39Z"
|
||||
android:fillColor="#F4F4F4"/>
|
||||
<path
|
||||
android:pathData="M102,5C83.22,5 68,20.22 68,39C68,57.78 83.22,73 102,73C120.78,73 136,57.78 136,39C136,20.22 120.78,5 102,5Z"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M112.67,45.67H91.33M91.33,45.67L96.67,40.33M91.33,45.67L96.67,51M91.33,32.33H112.67M112.67,32.33L107.33,27M112.67,32.33L107.33,37.67"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M130,39C130,21.33 144.33,7 162,7C179.67,7 194,21.33 194,39C194,56.67 179.67,71 162,71C144.33,71 130,56.67 130,39Z"
|
||||
android:fillColor="#75E0A7"/>
|
||||
<path
|
||||
android:pathData="M162,5C143.22,5 128,20.22 128,39C128,57.78 143.22,73 162,73C180.78,73 196,57.78 196,39C196,20.22 180.78,5 162,5Z"
|
||||
android:strokeWidth="4"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M154,44.33C154,47.28 156.39,49.67 159.33,49.67H164.67C167.61,49.67 170,47.28 170,44.33C170,41.39 167.61,39 164.67,39H159.33C156.39,39 154,36.61 154,33.67C154,30.72 156.39,28.33 159.33,28.33H164.67C167.61,28.33 170,30.72 170,33.67M162,25.67V52.33"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#085D3A"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M0.5,10C0.5,4.753 4.753,0.5 10,0.5C15.247,0.5 19.5,4.753 19.5,10C19.5,15.247 15.247,19.5 10,19.5C4.753,19.5 0.5,15.247 0.5,10Z"
|
||||
android:fillColor="#E8E8E8"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M0.5,10C0.5,4.753 4.753,0.5 10,0.5C15.247,0.5 19.5,4.753 19.5,10C19.5,15.247 15.247,19.5 10,19.5C4.753,19.5 0.5,15.247 0.5,10Z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"/>
|
||||
<path
|
||||
android:pathData="M10,6L10,6A4,4 0,0 1,14 10L14,10A4,4 0,0 1,10 14L10,14A4,4 0,0 1,6 10L6,10A4,4 0,0 1,10 6z"
|
||||
android:fillColor="#343031"/>
|
||||
</vector>
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M0.5,10C0.5,4.753 4.753,0.5 10,0.5C15.247,0.5 19.5,4.753 19.5,10C19.5,15.247 15.247,19.5 10,19.5C4.753,19.5 0.5,15.247 0.5,10Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M0.5,10C0.5,4.753 4.753,0.5 10,0.5C15.247,0.5 19.5,4.753 19.5,10C19.5,15.247 15.247,19.5 10,19.5C4.753,19.5 0.5,15.247 0.5,10Z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M10,6L10,6A4,4 0,0 1,14 10L14,10A4,4 0,0 1,10 14L10,14A4,4 0,0 1,6 10L6,10A4,4 0,0 1,10 6z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M0.5,10C0.5,4.753 4.753,0.5 10,0.5C15.247,0.5 19.5,4.753 19.5,10C19.5,15.247 15.247,19.5 10,19.5C4.753,19.5 0.5,15.247 0.5,10Z"
|
||||
android:fillColor="#343031"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M0.5,10C0.5,4.753 4.753,0.5 10,0.5C15.247,0.5 19.5,4.753 19.5,10C19.5,15.247 15.247,19.5 10,19.5C4.753,19.5 0.5,15.247 0.5,10Z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#939091"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<path
|
||||
android:pathData="M0.5,10C0.5,4.753 4.753,0.5 10,0.5C15.247,0.5 19.5,4.753 19.5,10C19.5,15.247 15.247,19.5 10,19.5C4.753,19.5 0.5,15.247 0.5,10Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M0.5,10C0.5,4.753 4.753,0.5 10,0.5C15.247,0.5 19.5,4.753 19.5,10C19.5,15.247 15.247,19.5 10,19.5C4.753,19.5 0.5,15.247 0.5,10Z"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#C0BFB1"/>
|
||||
</vector>
|
|
@ -0,0 +1,13 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="14dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="14"
|
||||
android:viewportHeight="14">
|
||||
<path
|
||||
android:pathData="M13,1L1,13M1,1L13,13"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#D2D1D2"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#454243"/>
|
||||
<path
|
||||
android:pathData="M17.5,19.583L19.166,21.25L22.916,17.5M26.666,20C26.666,24.09 22.205,27.065 20.581,28.013C20.397,28.12 20.305,28.174 20.174,28.202C20.073,28.223 19.926,28.223 19.825,28.202C19.695,28.174 19.603,28.12 19.418,28.013C17.795,27.065 13.333,24.09 13.333,20V16.015C13.333,15.349 13.333,15.015 13.442,14.729C13.538,14.476 13.695,14.25 13.898,14.071C14.128,13.869 14.439,13.752 15.063,13.518L19.531,11.842C19.705,11.777 19.791,11.745 19.881,11.732C19.959,11.72 20.04,11.72 20.119,11.732C20.208,11.745 20.295,11.777 20.468,11.842L24.936,13.518C25.56,13.752 25.872,13.869 26.102,14.071C26.305,14.25 26.461,14.476 26.557,14.729C26.666,15.015 26.666,15.349 26.666,16.015V20Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#EBEBE6"/>
|
||||
<path
|
||||
android:pathData="M17.5,19.583L19.167,21.25L22.917,17.5M26.667,20C26.667,24.09 22.205,27.065 20.582,28.013C20.397,28.12 20.305,28.174 20.175,28.202C20.074,28.223 19.927,28.223 19.825,28.202C19.695,28.174 19.603,28.12 19.419,28.013C17.795,27.065 13.333,24.09 13.333,20V16.015C13.333,15.349 13.333,15.015 13.443,14.729C13.539,14.476 13.695,14.25 13.898,14.071C14.128,13.869 14.44,13.752 15.064,13.518L19.532,11.842C19.705,11.777 19.792,11.745 19.881,11.732C19.96,11.72 20.04,11.72 20.119,11.732C20.208,11.745 20.295,11.777 20.468,11.842L24.937,13.518C25.56,13.752 25.872,13.869 26.102,14.071C26.305,14.25 26.462,14.476 26.558,14.729C26.667,15.015 26.667,15.349 26.667,16.015V20Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#454243"/>
|
||||
<path
|
||||
android:pathData="M27.044,20.744C26.813,22.919 25.58,24.957 23.541,26.135C20.153,28.091 15.821,26.93 13.865,23.542L13.657,23.181M12.955,19.256C13.187,17.081 14.419,15.043 16.458,13.866C19.846,11.91 24.178,13.071 26.134,16.458L26.343,16.819M12.911,25.055L13.521,22.778L15.798,23.389M24.202,16.612L26.479,17.222L27.089,14.945"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#EBEBE6"/>
|
||||
<path
|
||||
android:pathData="M27.044,20.744C26.813,22.919 25.58,24.957 23.541,26.135C20.153,28.091 15.821,26.93 13.865,23.542L13.657,23.181M12.955,19.256C13.187,17.081 14.419,15.043 16.458,13.866C19.846,11.91 24.178,13.071 26.134,16.458L26.343,16.819M12.911,25.055L13.521,22.778L15.798,23.389M24.202,16.612L26.479,17.222L27.089,14.945"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#454243"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M10,10h20v20h-20z"/>
|
||||
<path
|
||||
android:pathData="M15,15L16.667,13.333M16.667,13.333L15,11.667M16.667,13.333H15C13.159,13.333 11.667,14.826 11.667,16.667M25,25L23.333,26.667M23.333,26.667L25,28.333M23.333,26.667H25C26.841,26.667 28.333,25.174 28.333,23.333M21.181,21.181C21.833,21.492 22.563,21.667 23.333,21.667C26.095,21.667 28.333,19.428 28.333,16.667C28.333,13.905 26.095,11.667 23.333,11.667C20.572,11.667 18.333,13.905 18.333,16.667C18.333,17.437 18.507,18.167 18.819,18.819M21.667,23.333C21.667,26.095 19.428,28.333 16.667,28.333C13.905,28.333 11.667,26.095 11.667,23.333C11.667,20.572 13.905,18.333 16.667,18.333C19.428,18.333 21.667,20.572 21.667,23.333Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,20 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#EBEBE6"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M10,10h20v20h-20z"/>
|
||||
<path
|
||||
android:pathData="M15,15L16.667,13.333M16.667,13.333L15,11.667M16.667,13.333H15C13.159,13.333 11.667,14.826 11.667,16.667M25,25L23.333,26.667M23.333,26.667L25,28.333M23.333,26.667H25C26.841,26.667 28.333,25.174 28.333,23.333M21.181,21.181C21.833,21.492 22.563,21.667 23.333,21.667C26.095,21.667 28.333,19.428 28.333,16.667C28.333,13.905 26.095,11.667 23.333,11.667C20.572,11.667 18.333,13.905 18.333,16.667C18.333,17.437 18.507,18.167 18.819,18.819M21.667,23.333C21.667,26.095 19.428,28.333 16.667,28.333C13.905,28.333 11.667,26.095 11.667,23.333C11.667,20.572 13.905,18.333 16.667,18.333C19.428,18.333 21.667,20.572 21.667,23.333Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#454243"/>
|
||||
<path
|
||||
android:pathData="M26.666,15L17.5,24.167L13.333,20"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#EBEBE6"/>
|
||||
<path
|
||||
android:pathData="M26.667,15L17.5,24.167L13.333,20"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#454243"/>
|
||||
<path
|
||||
android:pathData="M25,15L15,25M15,15L25,25"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E8E8E8"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<path
|
||||
android:pathData="M0,20C0,8.954 8.954,0 20,0C31.046,0 40,8.954 40,20C40,31.046 31.046,40 20,40C8.954,40 0,31.046 0,20Z"
|
||||
android:fillColor="#EBEBE6"/>
|
||||
<path
|
||||
android:pathData="M25,15L15,25M15,15L25,25"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.66667"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#231F20"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
|
@ -0,0 +1,30 @@
|
|||
<resources>
|
||||
<string name="exchange_rate_info_title_1">IP Address Protection</string>
|
||||
<string name="exchange_rate_info_subtitle_1">Zashi\’s currency conversion feature doesn\’t compromise your IP
|
||||
address.</string>
|
||||
<string name="exchange_rate_info_title_2">Rate Refresh</string>
|
||||
<string name="exchange_rate_info_subtitle_2">The rate is refreshed automatically and can also be refreshed manually.</string>
|
||||
|
||||
<string name="exchange_rate_unavailable_title">Exchange rate unavailable</string>
|
||||
<string name="exchange_rate_unavailable_subtitle">We tried but we couldn\’t refresh the exchange rate
|
||||
for you. Check your connection, relaunch the app, and we\’ll try again.</string>
|
||||
<string name="exchange_rate_opt_in_title">New Feature</string>
|
||||
<string name="exchange_rate_opt_in_subtitle">Currency Conversion</string>
|
||||
<string name="exchange_rate_opt_in_primary_btn">Review</string>
|
||||
|
||||
<string name="exchange_rate_opt_in_enable">Enable</string>
|
||||
<string name="exchange_rate_opt_in_save">Save changes</string>
|
||||
<string name="exchange_rate_opt_in_skip">Skip for now</string>
|
||||
<string name="exchange_rate_opt_in_note">Note for the super privacy-conscious: Because we pull the conversion rate
|
||||
from exchanges, an exchange might be able to see that the exchange rate was queried before a transaction occurred.</string>
|
||||
|
||||
<string name="exchange_rate_opt_in_option_title">Enable</string>
|
||||
<string name="exchange_rate_opt_in_option_subtitle">Show me the currency conversion.</string>
|
||||
<string name="exchange_rate_opt_out_option_title">Disable</string>
|
||||
<string name="exchange_rate_opt_out_option_subtitle">Don\’t show the currency conversion.</string>
|
||||
<string name="exchange_rate_opt_in_description">Display your balance and payment amounts in USD.\nYou can manage
|
||||
this feature in Advanced Settings.</string>
|
||||
<string name="exchange_rate_opt_in_description_settings">Display your balance and payment amounts in USD.\n
|
||||
Zashi\’s currency conversion feature protects your IP address at all times.</string>
|
||||
|
||||
</resources>
|
Loading…
Reference in New Issue