Updated on 2026-08-14
This commit is contained in:
commit
c3522320ae
660 changed files with 10672 additions and 6904 deletions
|
|
@ -56,6 +56,7 @@ dependencies {
|
|||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.tokens)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.addcustomtoken.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
|||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.AlertState
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
|
|
@ -287,17 +289,10 @@ private fun TokenTextFieldTitle(state: TextFieldState?, title: String) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseDerivationScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
AddCustomTokenScreen(state = AddCustomTokenPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseDerivationScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ChooseDerivationScreen() {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenScreen(state = AddCustomTokenPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.addcustomtoken.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -13,6 +14,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock
|
||||
|
|
@ -98,17 +100,10 @@ private fun DerivationsList(state: ChooseDerivationState) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseDerivationScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseDerivationScreen(state = ChooseDerivationPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseDerivationScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ChooseDerivationScreen() {
|
||||
TangemThemePreview {
|
||||
ChooseDerivationScreen(state = ChooseDerivationPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.addcustomtoken.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -13,6 +14,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.ui.components.NetworkItem
|
||||
|
|
@ -78,17 +80,10 @@ internal fun ChooseNetworkCustomScreen(state: ChooseNetworkState, modifier: Modi
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseNetworkCustomScreen(ChooseNetworkCustomPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ChooseNetworkScreen() {
|
||||
TangemThemePreview {
|
||||
ChooseNetworkCustomScreen(ChooseNetworkCustomPreviewData.state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.common.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -23,6 +24,7 @@ import com.tangem.core.ui.components.SpacerW12
|
|||
import com.tangem.core.ui.components.SpacerWMax
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
import com.tangem.managetokens.presentation.common.state.WalletState
|
||||
|
|
@ -137,19 +139,10 @@ private fun WalletItem(wallet: WalletState, selectedWallet: WalletState?, modifi
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseWalletScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseWalletScreen(
|
||||
state = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseWalletScreen_Dark() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_ChooseWalletScreen() {
|
||||
TangemThemePreview {
|
||||
ChooseWalletScreen(
|
||||
state = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.common.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.NetworkItemState
|
||||
|
|
@ -122,17 +124,10 @@ internal fun NetworkIcon(model: NetworkItemState, modifier: Modifier = Modifier)
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_NetworkItem_Light(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) {
|
||||
TangemTheme(isDark = false) {
|
||||
NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_NetworkItem_Dark(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_NetworkItem(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) {
|
||||
TangemThemePreview {
|
||||
NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.common.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -12,6 +13,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
|
|
@ -54,17 +56,10 @@ fun SimpleSelectionBlock(
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_SimpleSelectionBlock_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_SimpleSelectionBlock_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_SimpleSelectionBlock() {
|
||||
TangemThemePreview {
|
||||
SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -17,6 +18,7 @@ import com.tangem.core.ui.components.SpacerH
|
|||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.WarningCardTitleOnly
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.common.state.ChooseWalletState
|
||||
|
|
@ -187,20 +189,10 @@ private fun NonNativeNetworksHeader(onNonNativeNetworkHintClick: () -> Unit) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
ChooseNetworkScreen(
|
||||
state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded,
|
||||
walletState = ChooseWalletStatePreviewData.state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ChooseNetworkScreen_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ChooseNetworkScreen() {
|
||||
TangemThemePreview {
|
||||
ChooseNetworkScreen(
|
||||
state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded,
|
||||
walletState = ChooseWalletStatePreviewData.state,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -22,6 +23,7 @@ import com.tangem.core.ui.components.Keyboard
|
|||
import com.tangem.core.ui.components.SpacerH18
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.managetokens.presentation.addcustomtoken.ui.AddCustomTokenBottomSheet
|
||||
import com.tangem.managetokens.presentation.common.state.AlertState
|
||||
|
|
@ -177,23 +179,13 @@ private fun ManageTokensBottomSheet(selectedToken: TokenItemState.Loaded, state:
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ManageTokensScreen_LightTheme(
|
||||
private fun Preview_ManageTokensScreen(
|
||||
@PreviewParameter(ManageTokensConfigProvider::class)
|
||||
state: ManageTokensState,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
ManageTokensScreen(state) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ManageTokensScreen_DarkTheme(
|
||||
@PreviewParameter(ManageTokensConfigProvider::class)
|
||||
state: ManageTokensState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
ManageTokensScreen(state) {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -14,6 +15,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -49,17 +51,10 @@ internal fun AddCustomTokenButton(onButtonClick: () -> Unit, modifier: Modifier
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AddCustomTokenButton_Preview_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
AddCustomTokenButton(onButtonClick = { })
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AddCustomTokenButton_Preview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun AddCustomTokenButton_Preview() {
|
||||
TangemThemePreview {
|
||||
AddCustomTokenButton(onButtonClick = { })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
|
|
@ -18,6 +19,7 @@ import com.tangem.core.ui.components.SpacerW
|
|||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.managetokens.state.previewdata.DerivationNotificationStatePreviewData
|
||||
|
|
@ -127,17 +129,10 @@ private fun TextsBlock(title: TextReference, subtitle: TextReference) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ManageTokensScreen_LightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
DerivationNotification(DerivationNotificationStatePreviewData.state.config)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_ManageTokensScreen_DarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ManageTokensScreen() {
|
||||
TangemThemePreview {
|
||||
DerivationNotification(DerivationNotificationStatePreviewData.state.config)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.drawscope.DrawScope
|
|||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ private fun getValuePercentageForRange(value: Float, max: Float, min: Float): Fl
|
|||
@Preview(widthDp = 150, heightDp = 150, showBackground = true)
|
||||
@Composable
|
||||
private fun Chart_Positive_Preview() {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview(isDark = true) {
|
||||
PriceChangesChart(
|
||||
persistentListOf(1f, 2f, 4f, 1f, 5f),
|
||||
)
|
||||
|
|
@ -115,7 +116,7 @@ private fun Chart_Positive_Preview() {
|
|||
@Preview(widthDp = 150, heightDp = 150, showBackground = true)
|
||||
@Composable
|
||||
private fun Chart_Negative_Preview() {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview(isDark = true) {
|
||||
PriceChangesChart(
|
||||
persistentListOf(10f, 2f, 4f, 1f, 5f),
|
||||
)
|
||||
|
|
@ -125,7 +126,7 @@ private fun Chart_Negative_Preview() {
|
|||
@Preview(widthDp = 150, heightDp = 150, showBackground = true)
|
||||
@Composable
|
||||
private fun Chart_Neutral_Preview() {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview(isDark = true) {
|
||||
PriceChangesChart(
|
||||
persistentListOf(5f, 2f, 4f, 1f, 5f),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
|
|
@ -20,6 +21,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.managetokens.state.SearchBarState
|
||||
|
|
@ -107,20 +109,13 @@ private fun searchbarTextFieldColors(): TextFieldColors {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_TokensSearchBar_Light(
|
||||
private fun Preview_TokensSearchBar(
|
||||
@PreviewParameter(SearchBarkConfigProvider::class)
|
||||
state: SearchBarState,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TokensSearchBar(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_TokensSearchBar_Dark(@PreviewParameter(SearchBarkConfigProvider::class) state: SearchBarState) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
TokensSearchBar(state)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -18,6 +19,7 @@ import com.tangem.core.ui.components.buttons.PrimarySmallButton
|
|||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.managetokens.presentation.managetokens.state.TokenButtonType
|
||||
|
|
@ -66,17 +68,10 @@ internal fun TokenButton(type: TokenButtonType, onClick: () -> Unit, modifier: M
|
|||
}
|
||||
|
||||
@Preview(backgroundColor = 0xffffff, showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TokenButton_Light_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) {
|
||||
TangemTheme(isDark = false) {
|
||||
TokenButton(type = type, {})
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TokenButton_Dark_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun TokenButton_Preview(@PreviewParameter(TokenButtonTypeProvider::class) type: TokenButtonType) {
|
||||
TangemThemePreview {
|
||||
TokenButton(type = type, {})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.managetokens.presentation.managetokens.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Surface
|
||||
|
|
@ -14,6 +15,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.managetokens.presentation.managetokens.state.QuotesState
|
||||
import com.tangem.managetokens.presentation.managetokens.state.TokenItemState
|
||||
|
|
@ -182,17 +184,10 @@ private fun BaseSurface(modifier: Modifier = Modifier, content: @Composable () -
|
|||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_Tokens_LightTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
|
||||
TangemTheme(isDark = false) {
|
||||
TokenRowItem(state)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun Preview_Tokens_DarkTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_Tokens(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) {
|
||||
TangemThemePreview {
|
||||
TokenRowItem(state)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -21,6 +22,7 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState
|
||||
|
|
@ -204,11 +206,12 @@ private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: D
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SuggestionsBlockPreview_Light(
|
||||
private fun SuggestionsBlockPreview(
|
||||
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
SuggestionsBlock(
|
||||
suggestionsList = suggestions,
|
||||
onClick = {},
|
||||
|
|
@ -217,24 +220,12 @@ private fun SuggestionsBlockPreview_Light(
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SuggestionsBlockPreview_Dark(
|
||||
private fun ImportSeedPhraseScreenPreview(
|
||||
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
SuggestionsBlock(
|
||||
suggestionsList = suggestions,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ImportSeedPhraseScreenPreview_Light(
|
||||
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
ImportSeedPhraseScreen(
|
||||
ImportSeedPhraseState(
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ShowPassphraseInfoBottomSheetContent
|
||||
|
||||
|
|
@ -83,7 +84,7 @@ fun PassphraseInfoBottomSheetContent(content: ShowPassphraseInfoBottomSheetConte
|
|||
@Preview
|
||||
@Composable
|
||||
private fun PassphraseInfoBottomSheetContentPreview() {
|
||||
TangemTheme {
|
||||
TangemThemePreview {
|
||||
PassphraseInfoBottomSheetContent(ShowPassphraseInfoBottomSheetContent { })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionSubTitleText
|
||||
|
|
@ -164,9 +166,10 @@ private inline fun <T> VerticalGrid(
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun YourSeedPhraseScreenPreview_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
YourSeedPhraseScreen(
|
||||
state = YourSeedPhraseState(
|
||||
segmentSeedState = SegmentSeedState(
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.feature.qrscanning.inner.MLKitBarcodeAnalyzer
|
||||
import com.tangem.feature.qrscanning.navigation.QrScanningInnerRouter
|
||||
import com.tangem.feature.qrscanning.presentation.QrScanningContent
|
||||
|
|
@ -39,7 +39,7 @@ import kotlin.properties.Delegates
|
|||
internal class QrScanningFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var router: QrScanningRouter
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ dependencies {
|
|||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ package com.tangem.feature.referral.data
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.StartReferralBody
|
||||
import com.tangem.datasource.demo.DemoModeDatasource
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -15,7 +16,6 @@ import com.tangem.feature.referral.converters.ReferralConverter
|
|||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.feature.referral.domain.models.ReferralData
|
||||
import com.tangem.feature.referral.domain.models.TokenData
|
||||
import com.tangem.lib.auth.AuthProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.feature.referral.di
|
||||
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.demo.DemoModeDatasource
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.feature.referral.converters.ReferralConverter
|
||||
import com.tangem.feature.referral.data.ReferralRepositoryImpl
|
||||
import com.tangem.feature.referral.domain.ReferralRepository
|
||||
import com.tangem.lib.auth.AuthProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import androidx.compose.foundation.layout.systemBarsPadding
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.feature.referral.router.ReferralRouter
|
||||
import com.tangem.feature.referral.ui.ReferralScreen
|
||||
import com.tangem.feature.referral.viewmodels.ReferralViewModel
|
||||
|
|
@ -20,7 +20,7 @@ import javax.inject.Inject
|
|||
class ReferralFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel by viewModels<ReferralViewModel>()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -16,6 +17,7 @@ import androidx.compose.ui.unit.dp
|
|||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithAdditionalButtons
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
|
|
@ -58,17 +60,10 @@ private fun AgreementHtmlView(url: String) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AgreementBottomSheet_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
AgreementBottomSheetContent(url = "https://tangem.com/en/")
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AgreementBottomSheet_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_AgreementBottomSheet() {
|
||||
TangemThemePreview {
|
||||
AgreementBottomSheetContent(url = "https://tangem.com/en/")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
|
|
@ -53,19 +55,10 @@ private fun annotatedAgreementString(firstPart: String): AnnotatedString {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AgreementText_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClick = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_AgreementText_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_AgreementText() {
|
||||
TangemThemePreview {
|
||||
Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClick = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
|
@ -56,8 +57,8 @@ internal fun AwardText(
|
|||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_AwardItem_Light() {
|
||||
TangemTheme {
|
||||
private fun Preview_AwardItem() {
|
||||
TangemThemePreview {
|
||||
AwardText(
|
||||
startText = "startText",
|
||||
startTextColor = TangemTheme.colors.text.tertiary,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryEndIconButton
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -18,29 +21,23 @@ internal fun NonParticipateBottomBlock(onAgreementClick: () -> Unit, onParticipa
|
|||
firstPartResId = R.string.referral_tos_not_enroled_prefix,
|
||||
onClick = onAgreementClick,
|
||||
)
|
||||
PrimaryEndIconButton(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
|
||||
PrimaryButtonIconEnd(
|
||||
text = stringResource(id = R.string.referral_button_participate),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
onClick = onParticipateClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_NonParticipateBottomBlock_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
Column(Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
NonParticipateBottomBlock(onAgreementClick = {}, onParticipateClick = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_NonParticipateBottomBlock_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_NonParticipateBottomBlock() {
|
||||
TangemThemePreview {
|
||||
Column(Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
NonParticipateBottomBlock(onAgreementClick = {}, onParticipateClick = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,14 @@ package com.tangem.feature.referral.ui
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
|
|
@ -30,11 +25,13 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconStart
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.referral.domain.models.ExpectedAward
|
||||
import com.tangem.feature.referral.domain.models.ExpectedAwards
|
||||
import com.tangem.feature.referral.presentation.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
|
@ -43,8 +40,8 @@ internal fun ParticipateBottomBlock(
|
|||
code: String,
|
||||
shareLink: String,
|
||||
expectedAwards: ExpectedAwards?,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onAgreementClick: () -> Unit,
|
||||
onShowCopySnackbar: () -> Unit,
|
||||
onCopyClick: () -> Unit,
|
||||
onShareClick: () -> Unit,
|
||||
) {
|
||||
|
|
@ -61,7 +58,7 @@ internal fun ParticipateBottomBlock(
|
|||
AdditionalButtons(
|
||||
code = code,
|
||||
shareLink = shareLink,
|
||||
onShowCopySnackbar = onShowCopySnackbar,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onCopyClick = onCopyClick,
|
||||
onShareClick = onShareClick,
|
||||
)
|
||||
|
|
@ -110,9 +107,9 @@ private fun Awards(expectedAwards: ExpectedAwards) {
|
|||
val elementsCountToShowInLessMode = 3
|
||||
val isExpanded = remember { mutableStateOf(false) }
|
||||
|
||||
Divider(
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
HorizontalDivider(
|
||||
thickness = TangemTheme.dimens.size0_5,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
)
|
||||
AwardText(
|
||||
startText = if (expectedAwards.expectedAwards.isNotEmpty()) {
|
||||
|
|
@ -270,32 +267,40 @@ private fun PersonalCodeCard(code: String) {
|
|||
private fun AdditionalButtons(
|
||||
code: String,
|
||||
shareLink: String,
|
||||
onShowCopySnackbar: () -> Unit,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onCopyClick: () -> Unit,
|
||||
onShareClick: () -> Unit,
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val resources = LocalContext.current.resources
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
PrimaryStartIconButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
PrimaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_copy),
|
||||
iconResId = R.drawable.ic_copy_24,
|
||||
onClick = {
|
||||
onCopyClick.invoke()
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
clipboardManager.setText(AnnotatedString(code))
|
||||
onShowCopySnackbar()
|
||||
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = resources.getString(R.string.referral_promo_code_copied),
|
||||
duration = SnackbarDuration.Short,
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
PrimaryStartIconButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
PrimaryButtonIconStart(
|
||||
text = stringResource(id = R.string.common_share),
|
||||
iconResId = R.drawable.ic_share_24,
|
||||
onClick = {
|
||||
|
|
@ -303,6 +308,7 @@ private fun AdditionalButtons(
|
|||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
context.shareText(context.getString(R.string.referral_share_link, shareLink))
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -318,11 +324,12 @@ private fun Context.shareText(text: String) {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ParticipateBottomBlockPreview_Light(
|
||||
private fun ParticipateBottomBlockPreview(
|
||||
@PreviewParameter(ParticipateBottomBlockDataProvider::class) data: ParticipateBottomBlockData,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
Column(Modifier.background(TangemTheme.colors.background.secondary)) {
|
||||
ParticipateBottomBlock(
|
||||
purchasedWalletCount = data.purchasedWalletCount,
|
||||
|
|
@ -330,7 +337,7 @@ private fun ParticipateBottomBlockPreview_Light(
|
|||
shareLink = data.shareLink,
|
||||
expectedAwards = data.expectedAwards,
|
||||
onAgreementClick = data.onAgreementClick,
|
||||
onShowCopySnackbar = data.onShowCopySnackbar,
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onCopyClick = data.onCopyClick,
|
||||
onShareClick = data.onShareClick,
|
||||
)
|
||||
|
|
@ -339,42 +346,10 @@ private fun ParticipateBottomBlockPreview_Light(
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ParticipateBottomBlockPreview_Dark(
|
||||
@PreviewParameter(ParticipateBottomBlockDataProvider::class) state: ParticipateBottomBlockData,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
Column(Modifier.background(TangemTheme.colors.background.secondary)) {
|
||||
ParticipateBottomBlock(
|
||||
purchasedWalletCount = state.purchasedWalletCount,
|
||||
code = state.code,
|
||||
shareLink = state.shareLink,
|
||||
expectedAwards = state.expectedAwards,
|
||||
onAgreementClick = state.onAgreementClick,
|
||||
onShowCopySnackbar = state.onShowCopySnackbar,
|
||||
onCopyClick = state.onCopyClick,
|
||||
onShareClick = state.onShareClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun LessMoreButton_Light() {
|
||||
TangemTheme(isDark = false) {
|
||||
LessMoreButton(
|
||||
isExpanded = remember {
|
||||
mutableStateOf(false)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun LessMoreButton_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun LessMoreButtonPreview() {
|
||||
TangemThemePreview {
|
||||
LessMoreButton(
|
||||
isExpanded = remember {
|
||||
mutableStateOf(false)
|
||||
|
|
@ -420,7 +395,6 @@ private class ParticipateBottomBlockDataProvider : CollectionPreviewParameterPro
|
|||
purchasedWalletCount = 0,
|
||||
expectedAwards = null,
|
||||
),
|
||||
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.feature.referral.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -10,9 +12,7 @@ import androidx.compose.material3.*
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
|
|
@ -21,13 +21,15 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbar
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.referral.domain.models.ExpectedAward
|
||||
import com.tangem.feature.referral.domain.models.ExpectedAwards
|
||||
import com.tangem.feature.referral.models.DemoModeException
|
||||
|
|
@ -48,6 +50,8 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
|
|||
var isBottomSheetVisible by remember { mutableStateOf(value = false) }
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
|
||||
val snackbarHostState = remember(::SnackbarHostState)
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
|
|
@ -56,10 +60,26 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
|
|||
onBackClick = stateHolder.headerState.onBackClicked,
|
||||
)
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.padding(bottom = TangemTheme.dimens.spacing108),
|
||||
) {
|
||||
// TODO: use StateEvent
|
||||
if (stateHolder.errorSnackbar != null) {
|
||||
TangemSnackbar(data = it, actionOnNewLine = true)
|
||||
} else {
|
||||
CopiedTextSnackbar(it)
|
||||
}
|
||||
}
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
) {
|
||||
ReferralContent(
|
||||
stateHolder = stateHolder,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onAgreementClick = {
|
||||
stateHolder.analytics.onAgreementClicked.invoke()
|
||||
isBottomSheetVisible = true
|
||||
|
|
@ -68,6 +88,26 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
|
|||
)
|
||||
}
|
||||
|
||||
val errorSnackbar = stateHolder.errorSnackbar
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val resources = LocalContext.current.resources
|
||||
|
||||
SideEffect {
|
||||
if (errorSnackbar != null) {
|
||||
coroutineScope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable),
|
||||
actionLabel = resources.getString(R.string.warning_button_ok),
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
errorSnackbar.onOkClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReferralBottomSheet(
|
||||
sheetState = sheetState,
|
||||
isVisible = isBottomSheetVisible,
|
||||
|
|
@ -79,11 +119,10 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
|
|||
@Composable
|
||||
private fun ReferralContent(
|
||||
stateHolder: ReferralStateHolder,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onAgreementClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isCopyButtonPressed = remember { mutableStateOf(value = false) }
|
||||
|
||||
Box(modifier = modifier) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -93,14 +132,11 @@ private fun ReferralContent(
|
|||
item {
|
||||
ReferralInfo(
|
||||
stateHolder = stateHolder,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onAgreementClick = onAgreementClick,
|
||||
onShowCopySnackbar = { isCopyButtonPressed.value = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorSnackbarHost(errorSnackbar = stateHolder.errorSnackbar)
|
||||
CopySnackbarHost(isCopyButtonPressed = isCopyButtonPressed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,8 +167,8 @@ private fun Header() {
|
|||
@Composable
|
||||
private fun ReferralInfo(
|
||||
stateHolder: ReferralStateHolder,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onAgreementClick: () -> Unit,
|
||||
onShowCopySnackbar: () -> Unit,
|
||||
) {
|
||||
when (val state = stateHolder.referralInfoState) {
|
||||
is ReferralInfoState.ParticipantContent -> {
|
||||
|
|
@ -142,8 +178,8 @@ private fun ReferralInfo(
|
|||
code = state.code,
|
||||
shareLink = state.shareLink,
|
||||
expectedAwards = state.expectedAwards,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onAgreementClick = onAgreementClick,
|
||||
onShowCopySnackbar = onShowCopySnackbar,
|
||||
onCopyClick = stateHolder.analytics.onCopyClicked,
|
||||
onShareClick = stateHolder.analytics.onShareClicked,
|
||||
)
|
||||
|
|
@ -333,122 +369,19 @@ private fun ShimmerInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO() Replace component with component from ds
|
||||
@Composable
|
||||
private fun BoxScope.ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) {
|
||||
if (errorSnackbar != null) {
|
||||
val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
snackbar = {
|
||||
Snackbar(
|
||||
snackbarData = it,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
actionOnNewLine = true,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius8),
|
||||
containerColor = TangemTheme.colors.button.primary,
|
||||
contentColor = TangemTheme.colors.text.primary2,
|
||||
actionColor = TangemTheme.colors.text.primary2,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val actionLabel = stringResource(id = R.string.warning_button_ok)
|
||||
val message = getMessageForErrorSnackbar(errorSnackbar)
|
||||
SideEffect {
|
||||
coroutineScope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = message,
|
||||
actionLabel = actionLabel,
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
errorSnackbar.onOkClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO() Replace component with component from ds
|
||||
@Composable
|
||||
private fun BoxScope.CopySnackbarHost(isCopyButtonPressed: MutableState<Boolean>) {
|
||||
if (isCopyButtonPressed.value) {
|
||||
val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var snackbarSize by remember { mutableIntStateOf(value = 0) }
|
||||
val width = LocalConfiguration.current.screenWidthDp.dp
|
||||
val snackbarWidth = with(LocalDensity.current) { snackbarSize.toDp() }
|
||||
|
||||
SnackbarHost(
|
||||
hostState = snackbarHostState,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(start = (width - snackbarWidth).div(2), bottom = TangemTheme.dimens.spacing12)
|
||||
.fillMaxWidth(),
|
||||
snackbar = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.onSizeChanged { snackbarSize = it.width }
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius8),
|
||||
)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing16,
|
||||
vertical = TangemTheme.dimens.spacing14,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = it.visuals.message,
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val message = stringResource(id = R.string.referral_promo_code_copied)
|
||||
SideEffect {
|
||||
coroutineScope.launch {
|
||||
snackbarHostState.showSnackbar(
|
||||
message = message,
|
||||
duration = SnackbarDuration.Short,
|
||||
)
|
||||
isCopyButtonPressed.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getMessageForErrorSnackbar(errorSnackbar: ErrorSnackbar): String {
|
||||
return when (errorSnackbar.throwable) {
|
||||
is DemoModeException -> {
|
||||
stringResource(id = R.string.alert_demo_feature_disabled)
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (errorSnackbar.throwable.cause != null) {
|
||||
String.format(
|
||||
format = stringResource(id = R.string.referral_error_failed_to_load_info_with_reason),
|
||||
errorSnackbar.throwable.cause,
|
||||
)
|
||||
} else {
|
||||
stringResource(id = R.string.referral_error_failed_to_load_info)
|
||||
}
|
||||
}
|
||||
private fun Resources.getMessageForErrorSnackbar(throwable: Throwable): String {
|
||||
return when {
|
||||
throwable is DemoModeException -> getString(R.string.alert_demo_feature_disabled)
|
||||
throwable.cause != null -> getString(R.string.referral_error_failed_to_load_info_with_reason, throwable.cause)
|
||||
else -> getString(R.string.referral_error_failed_to_load_info)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Participant_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_ReferralScreen_Participant() {
|
||||
TangemThemePreview {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
|
|
@ -475,38 +408,10 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Participant_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.ParticipantContent(
|
||||
award = "10 USDT",
|
||||
networkName = "Tron",
|
||||
address = "ma80...zk8q2",
|
||||
discount = "10%",
|
||||
purchasedWalletCount = 3,
|
||||
code = "x4JdK",
|
||||
shareLink = "",
|
||||
url = "",
|
||||
expectedAwards = null,
|
||||
),
|
||||
errorSnackbar = ErrorSnackbar(DemoModeException()) {},
|
||||
analytics = Analytics(
|
||||
onAgreementClicked = {},
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Participant_With_Referrals_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_ReferralScreen_Participant_With_Referrals() {
|
||||
TangemThemePreview {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
|
|
@ -549,9 +454,10 @@ private fun Preview_ReferralScreen_Participant_With_Referrals_InLightTheme() {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_NonParticipant_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_ReferralScreen_NonParticipant() {
|
||||
TangemThemePreview {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
|
|
@ -574,53 +480,10 @@ private fun Preview_ReferralScreen_NonParticipant_InLightTheme() {
|
|||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_NonParticipant_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.NonParticipantContent(
|
||||
award = "10 USDT",
|
||||
networkName = "Tron",
|
||||
discount = "10%",
|
||||
url = "",
|
||||
onParticipateClicked = {},
|
||||
),
|
||||
errorSnackbar = null,
|
||||
analytics = Analytics(
|
||||
onAgreementClicked = {},
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Loading_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
referralInfoState = ReferralInfoState.Loading,
|
||||
errorSnackbar = null,
|
||||
analytics = Analytics(
|
||||
onAgreementClicked = {},
|
||||
onCopyClicked = {},
|
||||
onShareClicked = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_ReferralScreen_Loading_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_ReferralScreen_Loading() {
|
||||
TangemThemePreview {
|
||||
ReferralScreen(
|
||||
stateHolder = ReferralStateHolder(
|
||||
headerState = HeaderState(onBackClicked = {}),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ dependencies {
|
|||
/** Domain modules */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.InnerSendRouter
|
||||
import com.tangem.features.send.impl.presentation.state.StateRouter
|
||||
|
|
@ -26,7 +26,7 @@ import javax.inject.Inject
|
|||
internal class SendFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var router: SendRouter
|
||||
|
|
|
|||
|
|
@ -162,4 +162,28 @@ internal sealed class SendNotification(val config: NotificationConfig) {
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Cardano {
|
||||
|
||||
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Warning(
|
||||
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_coin_will_be_send_with_token_description,
|
||||
formatArgs = wrappedList(minAdaValue, tokenName),
|
||||
),
|
||||
)
|
||||
|
||||
data object InsufficientBalanceToTransferCoin : Error(
|
||||
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||
)
|
||||
|
||||
data class InsufficientBalanceToTransferToken(val tokenName: String) : Error(
|
||||
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_insufficient_balance_to_send_token_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,11 @@
|
|||
package com.tangem.features.send.impl.presentation.state
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.state.amount.SendAmountStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.common.SendSyncEditConverter
|
||||
import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter
|
||||
|
|
@ -20,18 +13,14 @@ import com.tangem.features.send.impl.presentation.state.fee.FeeSelectorState
|
|||
import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.fee.checkFeeCoverage
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientWalletListConverter
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
@Suppress("LongParameterList")
|
||||
internal class SendStateFactory(
|
||||
private val clickIntents: SendClickIntents,
|
||||
private val stateRouterProvider: Provider<StateRouter>,
|
||||
|
|
@ -41,7 +30,6 @@ internal class SendStateFactory(
|
|||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val feeCryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
|
||||
private val isTapHelpPreviewEnabledProvider: Provider<Boolean>,
|
||||
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
|
||||
) {
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
||||
|
|
@ -79,14 +67,6 @@ internal class SendStateFactory(
|
|||
isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider,
|
||||
)
|
||||
}
|
||||
private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientWalletListConverter()
|
||||
}
|
||||
private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientHistoryListConverter(
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
)
|
||||
}
|
||||
private val sendSyncEditConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendSyncEditConverter(currentStateProvider = currentStateProvider)
|
||||
}
|
||||
|
|
@ -132,156 +112,6 @@ internal class SendStateFactory(
|
|||
}
|
||||
//endregion
|
||||
|
||||
//region recipient
|
||||
fun onLoadedWalletsList(wallets: List<AvailableWallet?>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
wallets = recipientWalletListStateConverter.convert(wallets),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onLoadedHistoryList(txHistory: List<TxHistoryItem>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
recent = recipientHistoryListStateConverter.convert(txHistory),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
addressTextField = recipientState.addressTextField.copy(value = value),
|
||||
memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
|
||||
val isValidMemo = validateWalletMemoUseCase(
|
||||
memo = recipientState.memoTextField?.value.orEmpty(),
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
|
||||
isValidating = false,
|
||||
addressTextField = recipientState.addressTextField.copy(
|
||||
error = when {
|
||||
!isValidAddress -> resourceReference(R.string.send_recipient_address_error)
|
||||
isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet)
|
||||
else -> null
|
||||
},
|
||||
isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidationStarted(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(isValidating = true),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValueChange(value: String): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(value = value),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
|
||||
val isValidMemo = validateWalletMemoUseCase(
|
||||
memo = value,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
|
||||
isValidating = false,
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
isError = value.isNotEmpty() && !isValidMemo,
|
||||
isEnabled = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnXAddressMemoState(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
value = "",
|
||||
isEnabled = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
val isNotValid = isAddressInWallet || !isValidAddress
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
recent = recipientState.recent.map { recent ->
|
||||
recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
wallets = recipientState.wallets.map { wallet ->
|
||||
wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region send
|
||||
fun getIsAmountSubtractedState(isAmountSubtractAvailable: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
package com.tangem.features.send.impl.presentation.state.confirm
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.core.ui.utils.parseToBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.extensions.minimalAmount
|
||||
import com.tangem.domain.tokens.GetBalanceNotEnoughForFeeWarningUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.analytics.SendAnalyticEvents
|
||||
|
|
@ -23,6 +27,7 @@ import com.tangem.features.send.impl.presentation.state.fee.*
|
|||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
import com.tangem.features.send.impl.presentation.utils.getFiatString
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTezos
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -46,6 +51,7 @@ internal class SendNotificationFactory(
|
|||
private val clickIntents: SendClickIntents,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
|
||||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
) {
|
||||
|
||||
fun create(): Flow<ImmutableList<SendNotification>> = stateRouterProvider().currentState
|
||||
|
|
@ -80,8 +86,9 @@ internal class SendNotificationFactory(
|
|||
addFeeUnreachableNotification(feeState.feeSelectorState)
|
||||
addExceedBalanceNotification(feeValue, sendingAmount)
|
||||
addExceedsBalanceNotification(feeState.fee)
|
||||
addDustWarningNotification(feeValue, sendingAmount)
|
||||
addDustWarningNotificationForSpecificBlockchains(feeValue, sendingAmount)
|
||||
addTransactionLimitErrorNotification(feeValue, sendingAmount)
|
||||
|
||||
// warnings
|
||||
addExistentialWarningNotification(feeValue, amountValue)
|
||||
addFeeCoverageNotification(
|
||||
|
|
@ -92,6 +99,9 @@ internal class SendNotificationFactory(
|
|||
addHighFeeWarningNotification(amountValue, sendState.ignoreAmountReduce)
|
||||
addTooHighNotification(feeState.feeSelectorState)
|
||||
addTooLowNotification(feeState)
|
||||
|
||||
// blockchain specific
|
||||
addCardanoNotifications(sendingAmount, feeState.fee, state)
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
|
|
@ -279,23 +289,35 @@ internal class SendNotificationFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotification(
|
||||
feeAmount: BigDecimal,
|
||||
receivedAmount: BigDecimal,
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotificationForSpecificBlockchains(
|
||||
feeValue: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletProvider().walletId,
|
||||
cryptoCurrencyStatus.currency.network,
|
||||
) ?: return
|
||||
val isCardano = BlockchainUtils.isCardano(cryptoCurrencyStatusProvider().currency.network.id.value)
|
||||
|
||||
if (checkDustLimits(feeAmount, receivedAmount, dustValue)) {
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(dustValue.toPlainString()),
|
||||
)
|
||||
if (!isCardano) {
|
||||
addDustWarningNotification(feeValue, sendingAmount)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
|
||||
val change = when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
balance - (feeAmount + receivedAmount)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val balance = coinCryptoCurrencyStatusProvider().value.amount ?: BigDecimal.ZERO
|
||||
balance - feeAmount
|
||||
}
|
||||
}
|
||||
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
}
|
||||
|
||||
private fun MutableList<SendNotification>.addTooLowNotification(feeState: SendStates.FeeState) {
|
||||
val feeSelectorState = feeState.feeSelectorState as? FeeSelectorState.Content ?: return
|
||||
val multipleFees = feeSelectorState.fees as? TransactionFee.Choosable ?: return
|
||||
|
|
@ -397,13 +419,90 @@ internal class SendNotificationFactory(
|
|||
return Blockchain.fromNetworkId(this.currency.network.backendId) == Blockchain.Arbitrum
|
||||
}
|
||||
|
||||
private fun checkDustLimits(feeAmount: BigDecimal, receivedAmount: BigDecimal, dustValue: BigDecimal): Boolean {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
private suspend fun MutableList<SendNotification>.addCardanoNotifications(
|
||||
sendingAmount: BigDecimal,
|
||||
fee: Fee?,
|
||||
state: SendUiState,
|
||||
) {
|
||||
val sendingCurrency = cryptoCurrencyStatusProvider().currency
|
||||
if (!BlockchainUtils.isCardano(sendingCurrency.network.id.value)) return
|
||||
|
||||
val totalAmount = feeAmount + receivedAmount
|
||||
val change = balance - totalAmount
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
validateTransactionUseCase(
|
||||
amount = sendingAmount.convertToAmount(sendingCurrency),
|
||||
fee = fee ?: return,
|
||||
memo = state.recipientState?.memoTextField?.value,
|
||||
destination = requireNotNull(state.recipientState?.addressTextField?.value),
|
||||
userWalletId = userWalletProvider().walletId,
|
||||
network = sendingCurrency.network,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
addCardanoTransactionValidationError(
|
||||
error = it as? BlockchainSdkError.Cardano ?: return@fold,
|
||||
sendingCurrency = sendingCurrency,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
(fee as? Fee.CardanoToken)?.let {
|
||||
add(
|
||||
SendNotification.Cardano.MinAdaValueCharged(
|
||||
tokenName = sendingCurrency.name,
|
||||
minAdaValue = it.minAdaValue.parseBigDecimal(sendingCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addCardanoTransactionValidationError(
|
||||
error: BlockchainSdkError.Cardano,
|
||||
sendingCurrency: CryptoCurrency,
|
||||
) {
|
||||
when (error) {
|
||||
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
|
||||
add(SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||
when (sendingCurrency) {
|
||||
is CryptoCurrency.Coin -> SendNotification.Cardano.InsufficientBalanceToTransferCoin
|
||||
is CryptoCurrency.Token -> {
|
||||
SendNotification.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
||||
}
|
||||
}.let(::add)
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||
-> {
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletId = userWalletProvider().walletId,
|
||||
network = sendingCurrency.network,
|
||||
) ?: return
|
||||
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(
|
||||
amount = dustValue.parseBigDecimal(sendingCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<SendNotification>.addDustWarningNotification(
|
||||
feeValue: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val dustValue = currencyChecksRepository.getDustValue(
|
||||
userWalletProvider().walletId,
|
||||
cryptoCurrencyStatus.currency.network,
|
||||
) ?: return
|
||||
|
||||
if (checkDustLimits(feeValue, sendingAmount, dustValue)) {
|
||||
add(
|
||||
SendNotification.Error.MinimumAmountError(
|
||||
amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ internal sealed class SendTextField {
|
|||
val label: TextReference,
|
||||
val isError: Boolean = false,
|
||||
val error: TextReference? = null,
|
||||
val isValuePasted: Boolean,
|
||||
) : SendTextField()
|
||||
|
||||
data class RecipientMemo(
|
||||
|
|
@ -54,6 +55,7 @@ internal sealed class SendTextField {
|
|||
val error: TextReference? = null,
|
||||
val disabledText: TextReference,
|
||||
val isEnabled: Boolean,
|
||||
val isValuePasted: Boolean,
|
||||
) : SendTextField()
|
||||
|
||||
data class CustomFee(
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ internal object RecipientStatePreviewData {
|
|||
label = stringReference("Recipient"),
|
||||
isError = false,
|
||||
error = null,
|
||||
isValuePasted = false,
|
||||
),
|
||||
memoTextField = null,
|
||||
recent = persistentListOf(
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ internal object SendClickIntentsStub : SendClickIntents {
|
|||
|
||||
override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {}
|
||||
|
||||
override fun onRecipientMemoValueChange(value: String) {}
|
||||
override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) {}
|
||||
|
||||
override fun feeReload() {}
|
||||
|
||||
|
|
@ -64,7 +64,8 @@ internal object SendClickIntentsStub : SendClickIntents {
|
|||
reduceAmountBy: BigDecimal?,
|
||||
reduceAmountTo: BigDecimal?,
|
||||
clazz: Class<out SendNotification>,
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onNotificationCancel(clazz: Class<out SendNotification>) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package com.tangem.features.send.impl.presentation.state.recipient
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.StateRouter
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import timber.log.Timber
|
||||
|
||||
internal class RecipientSendFactory(
|
||||
private val stateRouterProvider: Provider<StateRouter>,
|
||||
private val currentStateProvider: Provider<SendUiState>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
|
||||
) {
|
||||
private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientWalletListConverter()
|
||||
}
|
||||
private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientHistoryListConverter(
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
)
|
||||
}
|
||||
|
||||
fun onLoadedWalletsList(wallets: List<AvailableWallet?>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
wallets = recipientWalletListStateConverter.convert(wallets),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onLoadedHistoryList(txHistory: List<TxHistoryItem>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
recent = recipientHistoryListStateConverter.convert(txHistory),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false, isValuePasted: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
addressTextField = recipientState.addressTextField.copy(value = value, isValuePasted = isValuePasted),
|
||||
memoTextField = recipientState.memoTextField?.copy(isEnabled = !isXAddress),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
|
||||
val isValidMemo = validateWalletMemoUseCase(
|
||||
memo = recipientState.memoTextField?.value.orEmpty(),
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
|
||||
isValidating = false,
|
||||
addressTextField = recipientState.addressTextField.copy(
|
||||
error = when {
|
||||
!isValidAddress -> resourceReference(R.string.send_recipient_address_error)
|
||||
isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet)
|
||||
else -> null
|
||||
},
|
||||
isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidationStarted(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(isValidating = true),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValueChange(value: String, isValuePasted: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
value = value,
|
||||
isValuePasted = isValuePasted,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientMemoValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
|
||||
val isValidMemo = validateWalletMemoUseCase(
|
||||
memo = value,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
|
||||
isValidating = false,
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
isError = value.isNotEmpty() && !isValidMemo,
|
||||
isEnabled = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getOnXAddressMemoState(): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
value = "",
|
||||
isEnabled = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
val isNotValid = isAddressInWallet || !isValidAddress
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
recent = recipientState.recent.map { recent ->
|
||||
recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
wallets = recipientState.wallets.map { wallet ->
|
||||
wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ internal class SendRecipientAddressFieldConverter(
|
|||
error = resourceReference(R.string.send_recipient_address_error),
|
||||
placeholder = resourceReference(R.string.send_enter_address_field),
|
||||
label = resourceReference(R.string.send_recipient),
|
||||
isValuePasted = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ internal class SendRecipientMemoFieldConverter(
|
|||
error = resourceReference(R.string.send_memo_destination_tag_error),
|
||||
disabledText = resourceReference(R.string.send_additional_field_already_included),
|
||||
isEnabled = true,
|
||||
isValuePasted = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.shareText
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiCurrentScreen
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiStateType
|
||||
import com.tangem.features.send.impl.presentation.utils.getFiatFormatted
|
||||
import com.tangem.features.send.impl.presentation.utils.getFiatString
|
||||
|
||||
@Composable
|
||||
|
|
@ -179,10 +179,10 @@ private fun SendingText(
|
|||
}
|
||||
|
||||
if (feeFiat != null && sendingFiat != null) {
|
||||
val sendingValue = getFiatFormatted(
|
||||
value = sendingFiat,
|
||||
currencySymbol = feeState.appCurrency.symbol,
|
||||
currencyCode = feeState.appCurrency.code,
|
||||
val sendingValue = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = sendingFiat,
|
||||
fiatCurrencySymbol = feeState.appCurrency.symbol,
|
||||
fiatCurrencyCode = feeState.appCurrency.code,
|
||||
)
|
||||
val feeValue = getFiatString(
|
||||
value = feeState.fee?.amount?.value,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.amount
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -8,6 +9,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.previewdata.AmountStatePreviewData
|
||||
|
|
@ -43,25 +45,12 @@ internal fun SendAmountContent(
|
|||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AmountFieldPreview_Light(
|
||||
private fun AmountFieldPreview(
|
||||
@PreviewParameter(AmountFieldPreviewProvider::class) amountState: SendStates.AmountState,
|
||||
) {
|
||||
TangemTheme {
|
||||
SendAmountContent(
|
||||
amountState = amountState,
|
||||
isBalanceHiding = false,
|
||||
clickIntents = SendClickIntentsStub,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AmountFieldPreview_Dark(
|
||||
@PreviewParameter(AmountFieldPreviewProvider::class) amountState: SendStates.AmountState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
SendAmountContent(
|
||||
amountState = amountState,
|
||||
isBalanceHiding = false,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.fee
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -18,6 +19,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.fee.FeeType
|
||||
|
|
@ -107,21 +109,12 @@ private fun FooterText(onReadMoreClick: () -> Unit) {
|
|||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SendSpeedSelectorPreview_Light(
|
||||
private fun SendSpeedSelectorPreview(
|
||||
@PreviewParameter(SendSpeedSelectorPreviewProvider::class) feeState: SendStates.FeeState,
|
||||
) {
|
||||
TangemTheme {
|
||||
SendSpeedSelector(state = feeState, clickIntents = SendClickIntentsStub)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun SendSpeedSelectorPreview_Dark(
|
||||
@PreviewParameter(SendSpeedSelectorPreviewProvider::class) feeState: SendStates.FeeState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
SendSpeedSelector(state = feeState, clickIntents = SendClickIntentsStub)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.recipient
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
|
|
@ -27,6 +28,7 @@ import com.tangem.core.ui.components.atoms.text.EllipsisText
|
|||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.R
|
||||
|
||||
|
|
@ -183,28 +185,12 @@ private fun ListItemLoading(modifier: Modifier = Modifier) {
|
|||
|
||||
// region preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ListItemWithIconPreview_Light(
|
||||
private fun ListItemWithIconPreview(
|
||||
@PreviewParameter(ListItemWithIconPreviewProvider::class) config: ListItemWithIconPreviewConfig,
|
||||
) {
|
||||
TangemTheme {
|
||||
ListItemWithIcon(
|
||||
title = config.title,
|
||||
subtitle = config.subtitle,
|
||||
subtitleEndOffset = config.subtitleEndOffset,
|
||||
subtitleIconRes = config.iconRes,
|
||||
onClick = {},
|
||||
isLoading = config.isLoading,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ListItemWithIconPreview_Dark(
|
||||
@PreviewParameter(ListItemWithIconPreviewProvider::class) config: ListItemWithIconPreviewConfig,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
ListItemWithIcon(
|
||||
title = config.title,
|
||||
subtitle = config.subtitle,
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ internal fun SendRecipientContent(
|
|||
)
|
||||
memoField(
|
||||
memoField = memoField,
|
||||
onMemoChange = clickIntents::onRecipientMemoValueChange,
|
||||
onMemoChange = { clickIntents.onRecipientMemoValueChange(it, true) },
|
||||
)
|
||||
listHeaderItem(
|
||||
titleRes = R.string.send_recipient_wallets_title,
|
||||
|
|
@ -116,6 +116,7 @@ private fun LazyListScope.addressItem(
|
|||
isError = isError,
|
||||
isLoading = isValidating,
|
||||
error = address.error,
|
||||
isValuePasted = address.isValuePasted,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
|
|
@ -141,6 +142,7 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM
|
|||
isError = memoField.isError,
|
||||
error = memoField.error,
|
||||
isReadOnly = !memoField.isEnabled,
|
||||
isValuePasted = memoField.isValuePasted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ internal fun TextFieldWithPaste(
|
|||
error: TextReference? = null,
|
||||
isError: Boolean = false,
|
||||
isReadOnly: Boolean = false,
|
||||
isValuePasted: Boolean = false,
|
||||
) {
|
||||
val (title, color) = when {
|
||||
isError && error != null -> error to TangemTheme.colors.text.warning
|
||||
|
|
@ -63,6 +64,7 @@ internal fun TextFieldWithPaste(
|
|||
placeholderColor = placeholderColor,
|
||||
onValueChange = onValueChange,
|
||||
readOnly = isReadOnly,
|
||||
isValuePasted = isValuePasted,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.spacing6),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -16,6 +17,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.ResizableText
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
|
|
@ -84,11 +86,10 @@ internal fun AmountBlock(
|
|||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AmountBlockPreview_Light(
|
||||
@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState,
|
||||
) {
|
||||
TangemTheme {
|
||||
private fun AmountBlockPreview(@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState) {
|
||||
TangemThemePreview {
|
||||
AmountBlock(
|
||||
amountState = value,
|
||||
isClickDisabled = false,
|
||||
|
|
@ -98,21 +99,6 @@ private fun AmountBlockPreview_Light(
|
|||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun AmountBlockPreview_Dark(
|
||||
@PreviewParameter(AmountBlockPreviewProvider::class) value: SendStates.AmountState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
AmountBlock(
|
||||
amountState = value,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class AmountBlockPreviewProvider : PreviewParameterProvider<SendStates.AmountState> {
|
||||
override val values: Sequence<SendStates.AmountState>
|
||||
get() = sequenceOf(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.rows.SelectorRowItem
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.features.send.impl.R
|
||||
|
|
@ -110,21 +112,10 @@ private fun BoxScope.FeeError(feeSelectorState: FeeSelectorState) {
|
|||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun FeeBlockPreview_Light(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) {
|
||||
TangemTheme {
|
||||
FeeBlock(
|
||||
feeState = value,
|
||||
isClickDisabled = true,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun FeeBlockPreview_Dark(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: SendStates.FeeState) {
|
||||
TangemThemePreview {
|
||||
FeeBlock(
|
||||
feeState = value,
|
||||
isClickDisabled = true,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.send
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendTextField
|
||||
|
|
@ -97,26 +99,12 @@ private fun MemoBlock(memo: SendTextField.RecipientMemo?) {
|
|||
|
||||
// region Preview
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun RecipientBlockPreview_Light(
|
||||
private fun RecipientBlockPreview(
|
||||
@PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState,
|
||||
) {
|
||||
TangemTheme {
|
||||
RecipientBlock(
|
||||
recipientState = value,
|
||||
isClickDisabled = true,
|
||||
isEditingDisabled = false,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun RecipientBlockPreview_Dark(
|
||||
@PreviewParameter(RecipientBlockPreviewProvider::class) value: SendStates.RecipientState,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
RecipientBlock(
|
||||
recipientState = value,
|
||||
isClickDisabled = true,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,8 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
|
|||
import com.tangem.core.ui.utils.BigDecimalFormatter.EMPTY_BALANCE_SIGN
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
private const val FIAT_DECIMALS = 2
|
||||
private const val CRYPTO_FEE_DECIMALS = 6
|
||||
private const val FEE_MINIMUM_VALUE = 0.01
|
||||
|
||||
internal fun getCryptoReference(amount: Amount?, isFeeApproximate: Boolean): TextReference? {
|
||||
if (amount == null) return null
|
||||
|
|
@ -37,27 +34,9 @@ internal fun getFiatReference(value: BigDecimal?, rate: BigDecimal?, appCurrency
|
|||
internal fun getFiatString(value: BigDecimal?, rate: BigDecimal?, appCurrency: AppCurrency): String {
|
||||
if (value == null || rate == null) return EMPTY_BALANCE_SIGN
|
||||
val feeValue = value.multiply(rate)
|
||||
return getFiatFormatted(feeValue, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
internal fun getFiatFormatted(value: BigDecimal?, currencyCode: String, currencySymbol: String): String {
|
||||
val scaled = value?.setScale(FIAT_DECIMALS, RoundingMode.UP) ?: BigDecimal.ZERO
|
||||
return if (scaled < BigDecimal(FEE_MINIMUM_VALUE)) {
|
||||
buildString {
|
||||
append(BigDecimalFormatter.CAN_BE_LOWER_SIGN)
|
||||
append(
|
||||
BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = BigDecimal(FEE_MINIMUM_VALUE),
|
||||
fiatCurrencyCode = currencyCode,
|
||||
fiatCurrencySymbol = currencySymbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = value,
|
||||
fiatCurrencyCode = currencyCode,
|
||||
fiatCurrencySymbol = currencySymbol,
|
||||
)
|
||||
}
|
||||
return BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = feeValue,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ internal interface SendClickIntents {
|
|||
// region Recipient
|
||||
fun onRecipientAddressValueChange(value: String, type: EnterAddressSource? = null)
|
||||
|
||||
fun onRecipientMemoValueChange(value: String)
|
||||
fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean = false)
|
||||
// endregion
|
||||
|
||||
// region Fee
|
||||
|
|
|
|||
|
|
@ -30,10 +30,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -53,6 +50,7 @@ import com.tangem.features.send.impl.presentation.state.*
|
|||
import com.tangem.features.send.impl.presentation.state.amount.AmountStateFactory
|
||||
import com.tangem.features.send.impl.presentation.state.confirm.SendNotificationFactory
|
||||
import com.tangem.features.send.impl.presentation.state.fee.*
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.RecipientSendFactory
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -100,6 +98,7 @@ internal class SendViewModel @Inject constructor(
|
|||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
isFeeApproximateUseCase: IsFeeApproximateUseCase,
|
||||
validateWalletMemoUseCase: ValidateWalletMemoUseCase,
|
||||
|
|
@ -133,10 +132,16 @@ internal class SendViewModel @Inject constructor(
|
|||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
feeCryptoCurrencyStatusProvider = Provider { feeCryptoCurrencyStatus },
|
||||
validateWalletMemoUseCase = validateWalletMemoUseCase,
|
||||
isTapHelpPreviewEnabledProvider = Provider { isTapHelpPreviewEnabled },
|
||||
)
|
||||
|
||||
private val recipientStateFactory = RecipientSendFactory(
|
||||
stateRouterProvider = Provider { stateRouter },
|
||||
currentStateProvider = Provider { uiState },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
validateWalletMemoUseCase = validateWalletMemoUseCase,
|
||||
)
|
||||
|
||||
private val amountStateFactory = AmountStateFactory(
|
||||
stateRouterProvider = Provider { stateRouter },
|
||||
currentStateProvider = Provider { uiState },
|
||||
|
|
@ -178,6 +183,7 @@ internal class SendViewModel @Inject constructor(
|
|||
clickIntents = this,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
getBalanceNotEnoughForFeeWarningUseCase = getBalanceNotEnoughForFeeWarningUseCase,
|
||||
validateTransactionUseCase = validateTransactionUseCase,
|
||||
)
|
||||
|
||||
private val sendScreenAnalyticSender by lazy(LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -412,9 +418,9 @@ internal class SendViewModel @Inject constructor(
|
|||
.orEmpty()
|
||||
}.onSuccess { result ->
|
||||
userWallets = result
|
||||
uiState = stateFactory.onLoadedWalletsList(wallets = userWallets)
|
||||
uiState = recipientStateFactory.onLoadedWalletsList(wallets = userWallets)
|
||||
}.onFailure {
|
||||
uiState = stateFactory.onLoadedWalletsList(wallets = emptyList())
|
||||
uiState = recipientStateFactory.onLoadedWalletsList(wallets = emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -454,7 +460,7 @@ internal class SendViewModel @Inject constructor(
|
|||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
).getOrElse { emptyList() }
|
||||
uiState = stateFactory.onLoadedHistoryList(txHistory = txHistoryList)
|
||||
uiState = recipientStateFactory.onLoadedHistoryList(txHistory = txHistoryList)
|
||||
}
|
||||
|
||||
private fun onStateActive() {
|
||||
|
|
@ -588,7 +594,7 @@ internal class SendViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
// endregion
|
||||
|
||||
// region amount state clicks
|
||||
override fun onCurrencyChangeClick(isFiat: Boolean) {
|
||||
|
|
@ -614,23 +620,24 @@ internal class SendViewModel @Inject constructor(
|
|||
override fun onRecipientAddressValueChange(value: String, type: EnterAddressSource?) {
|
||||
viewModelScope.launch {
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
uiState = stateFactory.onRecipientAddressValueChange(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidationStarted()
|
||||
uiState = recipientStateFactory.onRecipientAddressValueChange(value, isValuePasted = type != null)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidationStarted()
|
||||
val isValidAddress = validateAddress(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidState(value, isValidAddress)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidState(value, isValidAddress)
|
||||
type?.let { analyticsEventHandler.send(SendAnalyticEvents.AddressEntered(it, isValidAddress)) }
|
||||
autoNextFromRecipient(type, isValidAddress)
|
||||
}
|
||||
}.saveIn(addressValidationJobHolder)
|
||||
}
|
||||
|
||||
override fun onRecipientMemoValueChange(value: String) {
|
||||
override fun onRecipientMemoValueChange(value: String, isValuePasted: Boolean) {
|
||||
viewModelScope.launch {
|
||||
if (!checkIfXrpAddressValue(value)) {
|
||||
uiState = stateFactory.getOnRecipientMemoValueChange(value)
|
||||
uiState = stateFactory.getOnRecipientAddressValidationStarted()
|
||||
val isValidAddress = validateAddress(uiState.recipientState?.addressTextField?.value.orEmpty())
|
||||
uiState = stateFactory.getOnRecipientMemoValidState(value, isValidAddress)
|
||||
uiState = recipientStateFactory.getOnRecipientMemoValueChange(value, isValuePasted)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidationStarted()
|
||||
val recipientState = uiState.getRecipientState(stateRouter.isEditState)
|
||||
val isValidAddress = validateAddress(recipientState?.addressTextField?.value.orEmpty())
|
||||
uiState = recipientStateFactory.getOnRecipientMemoValidState(value, isValidAddress)
|
||||
}
|
||||
}.saveIn(memoValidationJobHolder)
|
||||
}
|
||||
|
|
@ -649,16 +656,17 @@ internal class SendViewModel @Inject constructor(
|
|||
|
||||
private suspend fun checkIfXrpAddressValue(value: String): Boolean {
|
||||
return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress ->
|
||||
uiState = stateFactory.onRecipientAddressValueChange(value, isXAddress = true)
|
||||
uiState = stateFactory.getOnXAddressMemoState()
|
||||
uiState =
|
||||
recipientStateFactory.onRecipientAddressValueChange(value, isXAddress = true, isValuePasted = true)
|
||||
uiState = recipientStateFactory.getOnXAddressMemoState()
|
||||
val isValidAddress = validateAddress(decodedAddress.address)
|
||||
uiState = stateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidState(decodedAddress.address, isValidAddress)
|
||||
true
|
||||
} ?: false
|
||||
}
|
||||
|
||||
private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) {
|
||||
uiState = stateFactory.getHiddenRecentListState(
|
||||
uiState = recipientStateFactory.getHiddenRecentListState(
|
||||
isAddressInWallet = isAddressInWallet,
|
||||
isValidAddress = isValidAddress,
|
||||
)
|
||||
|
|
|
|||
1
features/staking/api/.gitignore
vendored
Normal file
1
features/staking/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
16
features/staking/api/build.gradle.kts
Normal file
16
features/staking/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("kotlin-parcelize")
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.staking.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.staking.api.featuretoggles
|
||||
|
||||
/**
|
||||
* Staking feature toggles
|
||||
*/
|
||||
interface StakingFeatureToggles {
|
||||
|
||||
/** Availability of staking */
|
||||
val isStakingEnabled: Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.staking.api.navigation
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
interface StakingRouter {
|
||||
|
||||
fun getEntryFragment(): Fragment
|
||||
}
|
||||
1
features/staking/impl/.gitignore
vendored
Normal file
1
features/staking/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
62
features/staking/impl/build.gradle.kts
Normal file
62
features/staking/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.staking.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.appCompat)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
|
||||
/** Other dependencies */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.material)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.lifecycle.compose)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.timber)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.navigation)
|
||||
implementation(deps.compose.navigation.hilt)
|
||||
implementation(deps.compose.constraintLayout)
|
||||
|
||||
/** Tangem SDKs */
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common)
|
||||
|
||||
/** Libs */
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.staking.api)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.staking.impl.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.features.staking.impl.featuretoggles.DefaultStakingFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* DI module provides implementation of [StakingFeatureToggles]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object StakingFeatureTogglesModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingFeatureToggles(featureTogglesManager: FeatureTogglesManager): StakingFeatureToggles {
|
||||
return DefaultStakingFeatureToggles(featureTogglesManager = featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.features.staking.impl.di
|
||||
|
||||
import com.tangem.features.staking.impl.navigation.DefaultStakingRouter
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
|
||||
/**
|
||||
* DI module provides implementation of [StakingRouter]
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
internal object StakingRouterModule {
|
||||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideStakingRouter(): StakingRouter {
|
||||
return DefaultStakingRouter()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.staking.impl.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
|
||||
/**
|
||||
* Default implementation of staking feature toggles
|
||||
*
|
||||
* @property featureTogglesManager manager for getting information about the availability of feature toggles
|
||||
*/
|
||||
internal class DefaultStakingFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : StakingFeatureToggles {
|
||||
|
||||
override val isStakingEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.staking.impl.navigation
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
internal class DefaultStakingRouter : InnerStakingRouter {
|
||||
|
||||
override fun getEntryFragment(): Fragment = TODO()
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.staking.impl.navigation
|
||||
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
|
||||
interface InnerStakingRouter : StakingRouter
|
||||
|
|
@ -31,6 +31,7 @@ dependencies {
|
|||
/** Domain */
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import arrow.core.right
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
|
|
@ -20,11 +21,10 @@ import com.tangem.datasource.api.express.models.response.SwapPairsWithProviders
|
|||
import com.tangem.datasource.api.express.models.response.TxDetails
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.swap.converters.*
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
|
|
@ -48,7 +48,7 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
private val tangemExpressApi: TangemExpressApi,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val walletsStateHolder: WalletsStateHolder,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val errorsDataConverter: ErrorsDataConverter,
|
||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||
moshi: Moshi,
|
||||
|
|
@ -249,6 +249,7 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
fromContractAddress: String,
|
||||
fromNetwork: String,
|
||||
toContractAddress: String,
|
||||
fromAddress: String,
|
||||
toNetwork: String,
|
||||
fromAmount: String,
|
||||
fromDecimals: Int,
|
||||
|
|
@ -266,6 +267,7 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
fromContractAddress = fromContractAddress,
|
||||
fromNetwork = fromNetwork,
|
||||
toContractAddress = toContractAddress,
|
||||
fromAddress = fromAddress,
|
||||
toNetwork = toNetwork,
|
||||
fromAmount = fromAmount,
|
||||
fromDecimals = fromDecimals,
|
||||
|
|
@ -389,8 +391,8 @@ internal class DefaultSwapRepository @Inject constructor(
|
|||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = requireNotNull(
|
||||
walletsStateHolder.userWalletsListManager
|
||||
?.selectedUserWalletSync
|
||||
userWalletsListManager
|
||||
.selectedUserWalletSync
|
||||
?.scanResponse
|
||||
?.derivationStyleProvider,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.feature.swap.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
||||
import com.tangem.domain.common.extensions.toNetworkId
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import com.tangem.datasource.crypto.DataSignatureVerifier
|
|||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.swap.DefaultSwapRepository
|
||||
import com.tangem.feature.swap.DefaultSwapTransactionRepository
|
||||
import com.tangem.feature.swap.converters.ErrorsDataConverter
|
||||
|
|
@ -33,7 +33,7 @@ internal class SwapDataModule {
|
|||
coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
dataSignature: DataSignatureVerifier,
|
||||
walletManagerFacade: WalletManagersFacade,
|
||||
walletsStateHolder: WalletsStateHolder,
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
errorsDataConverter: ErrorsDataConverter,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): SwapRepository {
|
||||
|
|
@ -42,7 +42,7 @@ internal class SwapDataModule {
|
|||
tangemExpressApi = tangemExpressApi,
|
||||
coroutineDispatcher = coroutineDispatcher,
|
||||
walletManagersFacade = walletManagerFacade,
|
||||
walletsStateHolder = walletsStateHolder,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
errorsDataConverter = errorsDataConverter,
|
||||
dataSignatureVerifier = dataSignature,
|
||||
moshi = moshi,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ interface SwapRepository {
|
|||
fromContractAddress: String,
|
||||
fromNetwork: String,
|
||||
toContractAddress: String,
|
||||
fromAddress: String,
|
||||
toNetwork: String,
|
||||
fromAmount: String,
|
||||
fromDecimals: Int,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
|
|
@ -46,5 +47,6 @@ dependencies {
|
|||
implementation(deps.arrow.core)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.tangem.blockchain)
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.moshi)
|
||||
}
|
||||
|
|
@ -9,4 +9,13 @@ sealed class Warning {
|
|||
data class MinAmountWarning(val dustValue: BigDecimal) : Warning()
|
||||
|
||||
data class ReduceAmountWarning(val tezosFeeThreshold: BigDecimal) : Warning()
|
||||
|
||||
sealed class Cardano : Warning() {
|
||||
|
||||
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Cardano()
|
||||
|
||||
data object InsufficientBalanceToTransferCoin : Cardano()
|
||||
|
||||
data class InsufficientBalanceToTransferToken(val tokenName: String) : Cardano()
|
||||
}
|
||||
}
|
||||
|
|
@ -26,4 +26,6 @@ sealed class SwapTransactionState {
|
|||
data object UnknownError : SwapTransactionState()
|
||||
|
||||
data class ExpressError(val dataError: DataError) : SwapTransactionState()
|
||||
|
||||
data object DemoMode : SwapTransactionState()
|
||||
}
|
||||
|
|
@ -5,12 +5,15 @@ import arrow.core.getOrElse
|
|||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.extenstions.unwrap
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.common.extensions.minimalAmount
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -20,7 +23,10 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.EstimateFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -34,12 +40,12 @@ import com.tangem.feature.swap.domain.models.SwapAmount
|
|||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import com.tangem.lib.crypto.UserWalletManager
|
||||
import com.tangem.lib.crypto.models.*
|
||||
import com.tangem.lib.crypto.models.transactions.SendTxResult
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import com.tangem.utils.toFiatString
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
|
@ -59,6 +65,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val sendTransactionUseCase: SendTransactionUseCase,
|
||||
private val createTransactionUseCase: CreateTransactionUseCase,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val dispatcher: CoroutineDispatcherProvider,
|
||||
private val swapTransactionRepository: SwapTransactionRepository,
|
||||
|
|
@ -66,6 +73,8 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
private val appCurrencyRepository: AppCurrencyRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val initialToCurrencyResolver: InitialToCurrencyResolver,
|
||||
private val demoConfig: DemoConfig,
|
||||
private val transactionRepository: TransactionRepository,
|
||||
) : SwapInteractor {
|
||||
|
||||
private val estimateFeeUseCase by lazy(LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -343,6 +352,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
|
||||
txFee = TxFeeState.Empty,
|
||||
transactionFee = null,
|
||||
includeFeeInAmount = IncludeFeeInAmount.Excluded, // exclude for dex
|
||||
)
|
||||
}
|
||||
|
|
@ -372,6 +382,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
feeState: TxFeeState,
|
||||
minAdaValue: BigDecimal?,
|
||||
): List<Warning> {
|
||||
val fromToken = fromTokenStatus.currency
|
||||
val userWalletId = getSelectedWallet()?.walletId ?: return emptyList()
|
||||
|
|
@ -379,6 +390,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
manageExistentialDepositWarning(warnings, userWalletId, amount, fromToken)
|
||||
manageDustWarning(warnings, feeState, userWalletId, fromTokenStatus, amount)
|
||||
manageReduceAmountWarning(warnings, fromTokenStatus, amount)
|
||||
manageCardanoTransactionValidationWarnings(
|
||||
warnings = warnings,
|
||||
fromToken = fromToken,
|
||||
amount = amount,
|
||||
userWalletId = userWalletId,
|
||||
minAdaValue = minAdaValue,
|
||||
)
|
||||
return warnings
|
||||
}
|
||||
|
||||
|
|
@ -407,23 +425,35 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
amount: SwapAmount,
|
||||
) {
|
||||
if (BlockchainUtils.isCardano(fromTokenStatus.currency.network.id.value)) return
|
||||
|
||||
val fee = when (feeState) {
|
||||
TxFeeState.Empty -> BigDecimal.ZERO
|
||||
is TxFeeState.MultipleFeeState -> feeState.priorityFee.feeValue
|
||||
is TxFeeState.SingleFeeState -> feeState.fee.feeValue
|
||||
}
|
||||
val dust = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network)
|
||||
val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO
|
||||
if (dust != null &&
|
||||
!balance.isNullOrZero() &&
|
||||
amount.value < balance
|
||||
) {
|
||||
val change = balance - (amount.value + fee)
|
||||
val isChangeLowerThanDust = change < dust && change != BigDecimal.ZERO
|
||||
val isShowWarning = amount.value + fee < dust || isChangeLowerThanDust
|
||||
if (isShowWarning) {
|
||||
warnings.add(Warning.MinAmountWarning(dust))
|
||||
|
||||
val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromTokenStatus.currency.network) ?: return
|
||||
|
||||
val change = when (fromTokenStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val balance = fromTokenStatus.value.amount ?: BigDecimal.ZERO
|
||||
balance - (fee + amount.value)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val nativeTokenBalance = userWalletManager.getNativeTokenBalance(
|
||||
fromTokenStatus.currency.network.id.value,
|
||||
fromTokenStatus.currency.network.derivationPath.value,
|
||||
)
|
||||
|
||||
nativeTokenBalance?.value?.minus(fee) ?: BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
|
||||
if (amount.value < dustValue || isChangeLowerThanDust) {
|
||||
warnings.add(Warning.MinAmountWarning(dustValue))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -438,6 +468,72 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun manageCardanoTransactionValidationWarnings(
|
||||
warnings: MutableList<Warning>,
|
||||
fromToken: CryptoCurrency,
|
||||
amount: SwapAmount,
|
||||
userWalletId: UserWalletId,
|
||||
minAdaValue: BigDecimal?,
|
||||
) {
|
||||
transactionRepository.validateTransaction(
|
||||
amount = amount.value.convertToAmount(fromToken),
|
||||
fee = null,
|
||||
memo = null,
|
||||
destination = getTokenAddress(fromToken),
|
||||
userWalletId = userWalletId,
|
||||
network = fromToken.network,
|
||||
)
|
||||
.fold(
|
||||
onFailure = {
|
||||
addCardanoTransactionValidationError(
|
||||
warnings = warnings,
|
||||
error = it as? BlockchainSdkError.Cardano ?: return@fold,
|
||||
fromToken = fromToken,
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
},
|
||||
onSuccess = {
|
||||
minAdaValue?.let {
|
||||
warnings.add(
|
||||
Warning.Cardano.MinAdaValueCharged(
|
||||
tokenName = fromToken.name,
|
||||
minAdaValue = minAdaValue.parseBigDecimal(fromToken.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun addCardanoTransactionValidationError(
|
||||
warnings: MutableList<Warning>,
|
||||
error: BlockchainSdkError.Cardano,
|
||||
fromToken: CryptoCurrency,
|
||||
userWalletId: UserWalletId,
|
||||
) {
|
||||
when (error) {
|
||||
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
|
||||
Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name)
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||
when (fromToken) {
|
||||
is CryptoCurrency.Coin -> Warning.Cardano.InsufficientBalanceToTransferCoin
|
||||
is CryptoCurrency.Token -> {
|
||||
Warning.Cardano.InsufficientBalanceToTransferToken(fromToken.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||
-> {
|
||||
val dustValue = currencyChecksRepository.getDustValue(userWalletId, fromToken.network) ?: return
|
||||
|
||||
Warning.MinAmountWarning(dustValue)
|
||||
}
|
||||
}
|
||||
.let(warnings::add) // add warning to the list
|
||||
}
|
||||
|
||||
override suspend fun onSwap(
|
||||
swapProvider: SwapProvider,
|
||||
swapData: SwapDataModel?,
|
||||
|
|
@ -473,6 +569,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
currencyToGet = currencyToGet.currency,
|
||||
amountToSwap = amountToSwap,
|
||||
fee = fee,
|
||||
userWalletId = requireNotNull(getSelectedWallet()).walletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -523,29 +620,35 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
currencyToGet: CryptoCurrency,
|
||||
amountToSwap: String,
|
||||
fee: TxFee,
|
||||
userWalletId: UserWalletId,
|
||||
): SwapTransactionState {
|
||||
val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" }
|
||||
val amount = SwapAmount(amountDecimal, currencyToSend.decimals)
|
||||
val derivationPath = currencyToSend.network.derivationPath.value
|
||||
val result = transactionManager.sendTransaction(
|
||||
txData = SwapTxData(
|
||||
networkId = networkId,
|
||||
amountToSend = amountDecimal,
|
||||
currencyToSend = swapCurrencyConverter.convert(currencyToSend),
|
||||
feeAmount = fee.feeValue,
|
||||
gasLimit = fee.gasLimit,
|
||||
destinationAddress = swapData.transaction.txTo,
|
||||
dataToSign = (swapData.transaction as ExpressTransactionModel.DEX).txData,
|
||||
val txData = createTransactionUseCase(
|
||||
amount = amount.value.convertToAmount(currencyToSend),
|
||||
fee = getFeeForTransaction(
|
||||
fee = fee,
|
||||
blockchain = Blockchain.fromId(currencyToSend.network.id.value),
|
||||
),
|
||||
memo = null,
|
||||
destination = swapData.transaction.txTo,
|
||||
userWalletId = userWalletId,
|
||||
network = currencyToSend.network,
|
||||
hash = (swapData.transaction as ExpressTransactionModel.DEX).txData,
|
||||
isSwap = true,
|
||||
derivationPath = derivationPath,
|
||||
analyticsData = AnalyticsData(
|
||||
feeType = fee.feeType.getNameForAnalytics(),
|
||||
tokenSymbol = currencyToSend.symbol,
|
||||
),
|
||||
).getOrElse {
|
||||
Timber.e(it)
|
||||
return SwapTransactionState.UnknownError
|
||||
}
|
||||
|
||||
val result = sendTransactionUseCase(
|
||||
txData = txData,
|
||||
userWallet = requireNotNull(getSelectedWallet()),
|
||||
network = currencyToSend.network,
|
||||
)
|
||||
return when (result) {
|
||||
is SendTxResult.Success -> {
|
||||
return result.fold(
|
||||
ifRight = {
|
||||
storeLastCryptoCurrencyId(currencyToGet)
|
||||
SwapTransactionState.TxSent(
|
||||
fromAmount = amountFormatter.formatSwapAmountToUI(
|
||||
|
|
@ -561,13 +664,18 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
txHash = userWalletManager.getLastTransactionHash(networkId, derivationPath).orEmpty(),
|
||||
timestamp = System.currentTimeMillis(),
|
||||
)
|
||||
}
|
||||
SendTxResult.UserCancelledError -> SwapTransactionState.UserCancelled
|
||||
is SendTxResult.BlockchainSdkError -> SwapTransactionState.BlockchainError
|
||||
is SendTxResult.TangemSdkError -> SwapTransactionState.TangemSdkError
|
||||
is SendTxResult.NetworkError -> SwapTransactionState.NetworkError
|
||||
is SendTxResult.UnknownError -> SwapTransactionState.UnknownError
|
||||
}
|
||||
},
|
||||
ifLeft = {
|
||||
when (it) {
|
||||
SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled
|
||||
is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError
|
||||
is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError
|
||||
is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError
|
||||
is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode
|
||||
else -> SwapTransactionState.UnknownError
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
|
|
@ -583,13 +691,14 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromContractAddress = currencyToSend.currency.getContractAddress(),
|
||||
fromNetwork = currencyToSend.currency.network.backendId,
|
||||
toContractAddress = currencyToGet.currency.getContractAddress(),
|
||||
fromAddress = currencyToSend.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
toNetwork = currencyToGet.currency.network.backendId,
|
||||
fromAmount = amount.toStringWithRightOffset(),
|
||||
fromDecimals = amount.decimals,
|
||||
toDecimals = currencyToGet.currency.decimals,
|
||||
providerId = swapProvider.providerId,
|
||||
rateType = RateType.FLOAT,
|
||||
toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value ?: "",
|
||||
toAddress = currencyToGet.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
refundAddress = currencyToSend.value.networkAddress?.defaultAddress?.value,
|
||||
refundExtraId = null, // currently always null
|
||||
).getOrElse { return SwapTransactionState.ExpressError(it) }
|
||||
|
|
@ -601,10 +710,11 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
currencyToSend.currency.network.backendId,
|
||||
exchangeDataCex.txExtraId,
|
||||
)
|
||||
if (txExtras == null && exchangeDataCex.txExtraId != null) {
|
||||
val cardId = getSelectedWallet()?.scanResponse?.card?.cardId ?: return SwapTransactionState.UnknownError
|
||||
if (txExtras == null && exchangeDataCex.txExtraId != null && !demoConfig.isDemoCardId(cardId)) {
|
||||
return SwapTransactionState.UnknownError
|
||||
}
|
||||
val txData = walletManagersFacade.createTransaction(
|
||||
val txData = createTransactionUseCase(
|
||||
amount = amount.value.convertToAmount(currencyToSend.currency),
|
||||
fee = getFeeForTransaction(
|
||||
fee = txFee,
|
||||
|
|
@ -614,12 +724,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
destination = exchangeDataCex.txTo,
|
||||
userWalletId = userWalletId,
|
||||
network = currencyToSend.currency.network,
|
||||
)?.copy(
|
||||
extras = txExtras,
|
||||
)
|
||||
).getOrElse {
|
||||
Timber.e(it)
|
||||
return SwapTransactionState.UnknownError
|
||||
}.copy(extras = txExtras)
|
||||
|
||||
val result = sendTransactionUseCase(
|
||||
requireNotNull(txData),
|
||||
txData = txData,
|
||||
userWallet = requireNotNull(getSelectedWallet()),
|
||||
network = currencyToSend.currency.network,
|
||||
)
|
||||
|
|
@ -634,6 +745,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError
|
||||
is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError
|
||||
is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError
|
||||
is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode
|
||||
else -> SwapTransactionState.UnknownError
|
||||
}
|
||||
},
|
||||
|
|
@ -818,8 +930,16 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
val fromToken = fromTokenStatus.currency
|
||||
val toToken = toTokenStatus.currency
|
||||
return coroutineScope {
|
||||
val txFeeResult = getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId ->
|
||||
getUnhandledFee(
|
||||
amount = amount.value,
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = fromToken,
|
||||
)
|
||||
}
|
||||
|
||||
val txFee = if (provider.type == ExchangeProviderType.CEX) {
|
||||
getFeeForCex(amount, fromTokenStatus)
|
||||
getFeeForCex(txFeeResult, fromTokenStatus)
|
||||
} else {
|
||||
TxFeeState.Empty
|
||||
}
|
||||
|
|
@ -858,11 +978,13 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
isAllowedToSpend = isAllowedToSpend,
|
||||
isBalanceWithoutFeeEnough = isBalanceWithoutFeeEnough,
|
||||
txFee = txFee,
|
||||
transactionFee = txFeeResult?.getOrNull(),
|
||||
includeFeeInAmount = includeFeeInAmount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private suspend fun getQuotesState(
|
||||
exchangeProviderType: ExchangeProviderType,
|
||||
quoteDataModel: Either<DataError, QuoteModel>,
|
||||
|
|
@ -873,6 +995,7 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
isAllowedToSpend: Boolean,
|
||||
isBalanceWithoutFeeEnough: Boolean,
|
||||
txFee: TxFeeState,
|
||||
transactionFee: TransactionFee?,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
): SwapState {
|
||||
return quoteDataModel.fold(
|
||||
|
|
@ -886,7 +1009,12 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
swapData = null,
|
||||
txFeeState = txFee,
|
||||
).copy(
|
||||
warnings = manageWarnings(fromToken, amount, txFee),
|
||||
warnings = manageWarnings(
|
||||
fromTokenStatus = fromToken,
|
||||
amount = amount,
|
||||
feeState = txFee,
|
||||
minAdaValue = (transactionFee?.normal as? Fee.CardanoToken)?.minAdaValue,
|
||||
),
|
||||
)
|
||||
|
||||
when (exchangeProviderType) {
|
||||
|
|
@ -1048,13 +1176,14 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
fromContractAddress = fromToken.currency.getContractAddress(),
|
||||
fromNetwork = fromToken.currency.network.backendId,
|
||||
toContractAddress = toToken.currency.getContractAddress(),
|
||||
fromAddress = fromToken.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
toNetwork = toToken.currency.network.backendId,
|
||||
fromAmount = amount.toStringWithRightOffset(),
|
||||
fromDecimals = amount.decimals,
|
||||
toDecimals = toToken.currency.decimals,
|
||||
providerId = provider.providerId,
|
||||
rateType = RateType.FLOAT,
|
||||
toAddress = toToken.value.networkAddress?.defaultAddress?.value ?: "",
|
||||
toAddress = toToken.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
).fold(
|
||||
ifRight = { swapData ->
|
||||
val feeData = transactionManager.getFee(
|
||||
|
|
@ -1089,7 +1218,14 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
swapState.copy(
|
||||
permissionState = PermissionDataState.Empty,
|
||||
warnings = manageWarnings(fromToken, amount, txFeeState),
|
||||
warnings = manageWarnings(
|
||||
fromToken,
|
||||
amount,
|
||||
txFeeState,
|
||||
(feeData as? ProxyFees.SingleFee)?.let {
|
||||
(it.singleFee as? ProxyFee.CardanoToken)?.minAdaValue
|
||||
},
|
||||
),
|
||||
preparedSwapConfigState = PreparedSwapConfigState(
|
||||
isAllowedToSpend = true,
|
||||
isBalanceEnough = isBalanceIncludeFeeEnough,
|
||||
|
|
@ -1155,23 +1291,26 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun getFeeForCex(amount: SwapAmount, fromToken: CryptoCurrencyStatus): TxFeeState {
|
||||
getSelectedWalletSyncUseCase().getOrNull()?.walletId?.let { userWalletId ->
|
||||
val txFeeResult = estimateFeeUseCase(
|
||||
amount = amount.value,
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = fromToken.currency,
|
||||
).firstOrNull()
|
||||
return txFeeResult?.fold(
|
||||
ifLeft = {
|
||||
TxFeeState.Empty
|
||||
},
|
||||
ifRight = { txFee ->
|
||||
txFee.toTxFeeState(fromToken.currency)
|
||||
},
|
||||
) ?: TxFeeState.Empty
|
||||
}
|
||||
return TxFeeState.Empty
|
||||
private suspend fun getFeeForCex(
|
||||
txFeeResult: Either<GetFeeError, TransactionFee>?,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
): TxFeeState {
|
||||
return txFeeResult?.fold(
|
||||
ifLeft = { TxFeeState.Empty },
|
||||
ifRight = { txFee -> txFee.toTxFeeState(fromToken.currency) },
|
||||
) ?: TxFeeState.Empty
|
||||
}
|
||||
|
||||
private suspend fun getUnhandledFee(
|
||||
amount: BigDecimal,
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Either<GetFeeError, TransactionFee>? {
|
||||
return estimateFeeUseCase(
|
||||
amount = amount,
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
).firstOrNull()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
|||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.WalletsStateHolder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.swap.domain.*
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
|
|
@ -40,6 +41,7 @@ class SwapDomainModule {
|
|||
@SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
|
||||
@SwapScope sendTransactionUseCase: SendTransactionUseCase,
|
||||
@SwapScope createTransactionUseCase: CreateTransactionUseCase,
|
||||
quotesRepository: QuotesRepository,
|
||||
swapTransactionRepository: SwapTransactionRepository,
|
||||
appCurrencyRepository: AppCurrencyRepository,
|
||||
|
|
@ -48,6 +50,7 @@ class SwapDomainModule {
|
|||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
initialToCurrencyResolver: InitialToCurrencyResolver,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
transactionRepository: TransactionRepository,
|
||||
): SwapInteractor {
|
||||
return SwapInteractorImpl(
|
||||
transactionManager = transactionManager,
|
||||
|
|
@ -57,6 +60,7 @@ class SwapDomainModule {
|
|||
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
|
||||
getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase,
|
||||
sendTransactionUseCase = sendTransactionUseCase,
|
||||
createTransactionUseCase = createTransactionUseCase,
|
||||
quotesRepository = quotesRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatcher = coroutineDispatcherProvider,
|
||||
|
|
@ -65,6 +69,8 @@ class SwapDomainModule {
|
|||
currencyChecksRepository = currencyChecksRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
initialToCurrencyResolver = initialToCurrencyResolver,
|
||||
demoConfig = DemoConfig(),
|
||||
transactionRepository = transactionRepository,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -79,8 +85,8 @@ class SwapDomainModule {
|
|||
@SwapScope
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetSelectedWalletUseCase(walletsStateHolder: WalletsStateHolder): GetSelectedWalletSyncUseCase {
|
||||
return GetSelectedWalletSyncUseCase(walletsStateHolder = walletsStateHolder)
|
||||
fun providesGetSelectedWalletUseCase(userWalletsListManager: UserWalletsListManager): GetSelectedWalletSyncUseCase {
|
||||
return GetSelectedWalletSyncUseCase(userWalletsListManager = userWalletsListManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -106,13 +112,11 @@ class SwapDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCardTokensListUseCase {
|
||||
return GetCardTokensListUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -122,6 +126,15 @@ class SwapDomainModule {
|
|||
return IsDemoCardUseCase(config = DemoConfig())
|
||||
}
|
||||
|
||||
@SwapScope
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase {
|
||||
return CreateTransactionUseCase(
|
||||
transactionRepository = transactionRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@SwapScope
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ dependencies {
|
|||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
|
|
@ -56,6 +57,9 @@ dependencies {
|
|||
implementation(projects.features.swap.api)
|
||||
implementation(projects.features.tokendetails.api)
|
||||
|
||||
/** Libs */
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.compose.accompanist.webView)
|
||||
|
|
@ -67,5 +71,4 @@ dependencies {
|
|||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ data class SwapStateHolder(
|
|||
val alert: SwapWarning.GenericWarning? = null,
|
||||
val changeCardsButtonState: ChangeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
val providerState: ProviderState,
|
||||
val reduceAmountIgnore: Boolean, // ignore warning about reducing XTZ amount by 0.01
|
||||
|
||||
val fee: FeeItemState = FeeItemState.Empty,
|
||||
val permissionState: SwapPermissionState = SwapPermissionState.Empty,
|
||||
|
|
@ -111,6 +110,7 @@ sealed interface SwapWarning {
|
|||
val type: GenericWarningType = GenericWarningType.OTHER,
|
||||
val onClick: () -> Unit,
|
||||
) : SwapWarning
|
||||
|
||||
data class GeneralError(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class UnableToCoverFeeWarning(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class GeneralWarning(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
|
|
@ -118,6 +118,16 @@ sealed interface SwapWarning {
|
|||
data class TransactionInProgressWarning(val title: TextReference, val description: TextReference) : SwapWarning
|
||||
data class NeedReserveToCreateAccount(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
data class ReduceAmount(val notificationConfig: NotificationConfig) : SwapWarning
|
||||
|
||||
sealed interface Cardano : SwapWarning {
|
||||
val notificationConfig: NotificationConfig
|
||||
|
||||
data class MinAdaValueCharged(override val notificationConfig: NotificationConfig) : Cardano
|
||||
|
||||
data class InsufficientBalanceToTransferCoin(override val notificationConfig: NotificationConfig) : Cardano
|
||||
|
||||
data class InsufficientBalanceToTransferToken(override val notificationConfig: NotificationConfig) : Cardano
|
||||
}
|
||||
}
|
||||
|
||||
enum class GenericWarningType {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ data class UiActions(
|
|||
val onBackClicked: () -> Unit,
|
||||
val onMaxAmountSelected: () -> Unit,
|
||||
val onReduceAmount: (SwapAmount) -> Unit,
|
||||
val onReduceAmountIgnoreClick: () -> Unit,
|
||||
val openPermissionBottomSheet: () -> Unit,
|
||||
val onChangeApproveType: (ApproveType) -> Unit,
|
||||
// region new actions
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.feature.swap.router.CustomTabsManager
|
||||
import com.tangem.feature.swap.router.SwapNavScreen
|
||||
import com.tangem.feature.swap.router.SwapRouter
|
||||
|
|
@ -25,7 +25,7 @@ import javax.inject.Inject
|
|||
class SwapFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
lateinit var reduxNavController: ReduxNavController
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import com.tangem.feature.swap.models.states.ChooseFeeBottomSheetConfig
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
|
|
@ -168,7 +169,7 @@ private fun ChooseFeeBottomSheetContent_Preview() {
|
|||
),
|
||||
).toImmutableList()
|
||||
Column {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview(isDark = true) {
|
||||
ChooseFeeBottomSheetContent(
|
||||
ChooseFeeBottomSheetConfig(
|
||||
selectedFee = FeeType.NORMAL,
|
||||
|
|
@ -183,7 +184,7 @@ private fun ChooseFeeBottomSheetContent_Preview() {
|
|||
|
||||
SpacerH24()
|
||||
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
ChooseFeeBottomSheetContent(
|
||||
ChooseFeeBottomSheetConfig(
|
||||
selectedFee = FeeType.NORMAL,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.states.ChooseProviderBottomSheetConfig
|
||||
import com.tangem.feature.swap.models.states.PercentDifference
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
|
|
@ -128,7 +129,7 @@ private fun ChooseProviderBottomSheet_Preview() {
|
|||
alertText = stringReference("Unavailable"),
|
||||
),
|
||||
)
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
ChooseProviderBottomSheetContent(
|
||||
ChooseProviderBottomSheetConfig(
|
||||
selectedProviderId = "1",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.ui.components.rows.SimpleActionRow
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import com.tangem.feature.swap.models.states.FeeItemState
|
||||
|
||||
|
|
@ -63,13 +64,13 @@ private fun FeeItemPreview() {
|
|||
onClick = {},
|
||||
)
|
||||
Column {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
FeeItem(state = state)
|
||||
}
|
||||
|
||||
SpacerH24()
|
||||
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview(isDark = true) {
|
||||
FeeItem(state = state)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -27,6 +28,7 @@ import com.tangem.core.ui.components.RectangleShimmer
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.states.PercentDifference
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
|
||||
|
|
@ -390,25 +392,12 @@ private fun PermissionBadgeItem(modifier: Modifier = Modifier) {
|
|||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ProviderItemPreview_Light(
|
||||
private fun ProviderItemPreview(
|
||||
@PreviewParameter(ProviderItemParameterProvider::class) state: Pair<ProviderState, Boolean>,
|
||||
) {
|
||||
TangemTheme {
|
||||
ProviderItem(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
state = state.first,
|
||||
isSelected = state.second,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun ProviderItemPreview_Dark(
|
||||
@PreviewParameter(ProviderItemParameterProvider::class) state: Pair<ProviderState, Boolean>,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
TangemThemePreview {
|
||||
ProviderItem(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
state = state.first,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,6 @@ internal class StateBuilder(
|
|||
onShowPermissionBottomSheet = actions.openPermissionBottomSheet,
|
||||
providerState = ProviderState.Empty(),
|
||||
shouldShowMaxAmount = false,
|
||||
reduceAmountIgnore = false,
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
)
|
||||
}
|
||||
|
|
@ -217,7 +216,6 @@ internal class StateBuilder(
|
|||
val warnings = getWarningsForSuccessState(
|
||||
quoteModel = quoteModel,
|
||||
fromToken = fromToken,
|
||||
ignoreAmountReduce = uiStateHolder.reduceAmountIgnore,
|
||||
selectedFeeType = selectedFeeType,
|
||||
)
|
||||
val feeState = createFeeState(quoteModel.txFee, selectedFeeType)
|
||||
|
|
@ -317,11 +315,10 @@ internal class StateBuilder(
|
|||
private fun getWarningsForSuccessState(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
ignoreAmountReduce: Boolean,
|
||||
selectedFeeType: FeeType,
|
||||
): List<SwapWarning> {
|
||||
val warnings = mutableListOf<SwapWarning>()
|
||||
maybeAddDomainWarnings(quoteModel, warnings, ignoreAmountReduce)
|
||||
maybeAddDomainWarnings(quoteModel, warnings)
|
||||
maybeAddNeedReserveToCreateAccountWarning(quoteModel, warnings)
|
||||
maybeAddPermissionNeededWarning(quoteModel, warnings, fromToken)
|
||||
maybeAddNetworkFeeCoverageWarning(quoteModel, warnings, selectedFeeType)
|
||||
|
|
@ -357,11 +354,7 @@ internal class StateBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
private fun maybeAddDomainWarnings(
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
warnings: MutableList<SwapWarning>,
|
||||
ignoreAmountReduce: Boolean,
|
||||
) {
|
||||
private fun maybeAddDomainWarnings(quoteModel: SwapState.QuotesLoadedState, warnings: MutableList<SwapWarning>) {
|
||||
quoteModel.warnings.forEach {
|
||||
when (it) {
|
||||
is Warning.ExistentialDepositWarning -> {
|
||||
|
|
@ -399,23 +392,29 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
is Warning.ReduceAmountWarning -> {
|
||||
if (!ignoreAmountReduce) {
|
||||
warnings.add(
|
||||
SwapWarning.ReduceAmount(
|
||||
notificationConfig = createReduceAmountNotificationConfig(
|
||||
amount = it.tezosFeeThreshold.toPlainString(),
|
||||
onConfirmClick = {
|
||||
val fromAmount = quoteModel.fromTokenInfo.tokenAmount
|
||||
val patchedAmount = fromAmount.copy(
|
||||
value = fromAmount.value - it.tezosFeeThreshold,
|
||||
)
|
||||
actions.onReduceAmount(patchedAmount)
|
||||
},
|
||||
onDismissClick = actions.onReduceAmountIgnoreClick,
|
||||
),
|
||||
warnings.add(
|
||||
SwapWarning.ReduceAmount(
|
||||
notificationConfig = createReduceAmountNotificationConfig(
|
||||
amount = it.tezosFeeThreshold.toPlainString(),
|
||||
onConfirmClick = {
|
||||
val fromAmount = quoteModel.fromTokenInfo.tokenAmount
|
||||
val patchedAmount = fromAmount.copy(
|
||||
value = fromAmount.value - it.tezosFeeThreshold,
|
||||
)
|
||||
actions.onReduceAmount(patchedAmount)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
),
|
||||
)
|
||||
}
|
||||
Warning.Cardano.InsufficientBalanceToTransferCoin -> {
|
||||
warnings.add(createInsufficientBalanceToTransferCoin())
|
||||
}
|
||||
is Warning.Cardano.InsufficientBalanceToTransferToken -> {
|
||||
warnings.add(createInsufficientBalanceToTransferToken(tokenName = it.tokenName))
|
||||
}
|
||||
is Warning.Cardano.MinAdaValueCharged -> {
|
||||
warnings.add(createMinAdaValueCharged(minAdaValue = it.minAdaValue, tokenName = it.tokenName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1015,6 +1014,18 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
fun createDemoModeAlert(uiState: SwapStateHolder, onAlertClick: () -> Unit): SwapStateHolder {
|
||||
return uiState.copy(
|
||||
alert = SwapWarning.GenericWarning(
|
||||
title = resourceReference(id = R.string.warning_demo_mode_title),
|
||||
message = resourceReference(id = R.string.warning_demo_mode_message),
|
||||
onClick = onAlertClick,
|
||||
type = GenericWarningType.OTHER,
|
||||
),
|
||||
changeCardsButtonState = ChangeCardsButtonState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getProviderErrorMessage(dataError: DataError): TextReference? {
|
||||
return when (dataError) {
|
||||
is DataError.SwapsAreUnavailableNowError -> resourceReference(
|
||||
|
|
@ -1374,20 +1385,14 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
private fun createReduceAmountNotificationConfig(
|
||||
amount: String,
|
||||
onConfirmClick: () -> Unit,
|
||||
onDismissClick: () -> Unit,
|
||||
): NotificationConfig {
|
||||
private fun createReduceAmountNotificationConfig(amount: String, onConfirmClick: () -> Unit): NotificationConfig {
|
||||
return NotificationConfig(
|
||||
title = resourceReference(R.string.send_notification_high_fee_title),
|
||||
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(amount)),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
buttonsState = NotificationConfig.ButtonsState.PairButtonsConfig(
|
||||
primaryText = resourceReference(R.string.xtz_withdrawal_message_reduce, wrappedList(amount)),
|
||||
onPrimaryClick = onConfirmClick,
|
||||
secondaryText = resourceReference(R.string.xtz_withdrawal_message_ignore),
|
||||
onSecondaryClick = onDismissClick,
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.xtz_withdrawal_message_reduce, wrappedList(amount)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1431,6 +1436,42 @@ internal class StateBuilder(
|
|||
iconResId = R.drawable.img_attention_20,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMinAdaValueCharged(minAdaValue: String, tokenName: String): SwapWarning {
|
||||
return SwapWarning.Cardano.MinAdaValueCharged(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_coin_will_be_send_with_token_description,
|
||||
formatArgs = wrappedList(minAdaValue, tokenName),
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createInsufficientBalanceToTransferCoin(): SwapWarning {
|
||||
return SwapWarning.Cardano.InsufficientBalanceToTransferCoin(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createInsufficientBalanceToTransferToken(tokenName: String): SwapWarning {
|
||||
return SwapWarning.Cardano.InsufficientBalanceToTransferToken(
|
||||
NotificationConfig(
|
||||
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_insufficient_balance_to_send_token_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
iconResId = R.drawable.img_attention_20,
|
||||
),
|
||||
)
|
||||
}
|
||||
// end region
|
||||
|
||||
private fun getShortAddressValue(fullAddress: String): String {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -17,6 +18,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.ApprovePermissionButton
|
||||
import com.tangem.feature.swap.models.ApproveType
|
||||
import com.tangem.feature.swap.models.CancelPermissionButton
|
||||
|
|
@ -291,17 +293,10 @@ private fun getTitleForApproveType(approveType: ApproveType): String = when (app
|
|||
// region preview
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_AgreementBottomSheet_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
SwapPermissionBottomSheetContent(content = previewData)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_AgreementBottomSheet_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_AgreementBottomSheet() {
|
||||
TangemThemePreview {
|
||||
SwapPermissionBottomSheetContent(content = previewData)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -30,6 +31,7 @@ import com.tangem.core.ui.extensions.getActiveIconResByCoinId
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.domain.models.ui.FeeType
|
||||
import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
||||
import com.tangem.feature.swap.models.*
|
||||
|
|
@ -389,7 +391,8 @@ private fun SwapWarnings(warnings: List<SwapWarning>) {
|
|||
},
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
is SwapWarning.Cardano -> Notification(config = warning.notificationConfig)
|
||||
SwapWarning.InsufficientFunds -> Unit
|
||||
}
|
||||
SpacerH8()
|
||||
}
|
||||
|
|
@ -497,7 +500,6 @@ private val state = SwapStateHolder(
|
|||
providerState = ProviderState.Loading(),
|
||||
priceImpact = PriceImpact.Empty(),
|
||||
shouldShowMaxAmount = true,
|
||||
reduceAmountIgnore = false,
|
||||
tosState = TosState(
|
||||
tosLink = LegalState(
|
||||
title = stringReference("Terms of Use"),
|
||||
|
|
@ -513,9 +515,10 @@ private val state = SwapStateHolder(
|
|||
)
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SwapScreenContentPreview() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
SwapScreenContent(state = state, modifier = Modifier)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -303,7 +304,7 @@ private val title = TokenToSelectState.Title(
|
|||
@Preview
|
||||
@Composable
|
||||
private fun TokenScreenPreview() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
SwapSelectTokenScreen(
|
||||
state = SwapSelectTokenStateHolder(
|
||||
availableTokens = listOf(title, token, token, token).toImmutableList(),
|
||||
|
|
@ -320,7 +321,7 @@ private fun TokenScreenPreview() {
|
|||
@Preview
|
||||
@Composable
|
||||
private fun EmptyTokensListPreview() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview {
|
||||
EmptyTokensList()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -18,6 +19,7 @@ import com.tangem.core.ui.components.inputrow.InputRowImage
|
|||
import com.tangem.core.ui.components.transactions.TransactionDoneTitle
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType
|
||||
import com.tangem.feature.swap.models.SwapSuccessStateHolder
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
|
|
@ -167,19 +169,11 @@ private val state = SwapSuccessStateHolder(
|
|||
)
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_Success_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
private fun Preview_Success() {
|
||||
TangemThemePreview {
|
||||
SwapSuccessScreen(state) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_Success_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
SwapSuccessScreen(state) {}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion preview
|
||||
|
|
@ -38,6 +38,7 @@ import coil.request.ImageRequest
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
|
||||
import com.tangem.feature.swap.domain.models.ui.PriceImpact
|
||||
import com.tangem.feature.swap.models.TransactionCardType
|
||||
|
|
@ -498,7 +499,7 @@ private fun makePriceImpactBalanceWarning(value: String, priceImpactPercents: In
|
|||
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_TransactionCard_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
TransactionCardPreview()
|
||||
}
|
||||
}
|
||||
|
|
@ -506,7 +507,7 @@ private fun Preview_TransactionCard_InLightTheme() {
|
|||
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_TransactionCardWithPriceImpact_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
TransactionCardPreviewWithPriceImpact()
|
||||
}
|
||||
}
|
||||
|
|
@ -514,7 +515,7 @@ private fun Preview_TransactionCardWithPriceImpact_InLightTheme() {
|
|||
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_TransactionCardWithoutPriceImpact_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
TransactionCardPreviewWithoutPriceImpact()
|
||||
}
|
||||
}
|
||||
|
|
@ -522,7 +523,7 @@ private fun Preview_TransactionCardWithoutPriceImpact_InLightTheme() {
|
|||
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_TransactionCard_InDarkTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
TransactionCardPreview()
|
||||
}
|
||||
}
|
||||
|
|
@ -530,7 +531,7 @@ private fun Preview_TransactionCard_InDarkTheme() {
|
|||
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_TransactionCardWithPriceImpact_InDarkTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
TransactionCardPreviewWithPriceImpact()
|
||||
}
|
||||
}
|
||||
|
|
@ -538,7 +539,7 @@ private fun Preview_TransactionCardWithPriceImpact_InDarkTheme() {
|
|||
@Preview(widthDp = 328, heightDp = 116, showBackground = true)
|
||||
@Composable
|
||||
private fun Preview_TransactionCardWithoutPriceImpact_InDarkTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
TangemThemePreview(isDark = false) {
|
||||
TransactionCardPreviewWithoutPriceImpact()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
|
|
@ -14,6 +15,7 @@ import com.google.accompanist.web.rememberWebViewState
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.states.WebViewBottomSheetConfig
|
||||
|
||||
@Composable
|
||||
|
|
@ -55,21 +57,10 @@ private fun WebViewBottomSheetContent(content: WebViewBottomSheetConfig) {
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_WebViewBottomSheetContent_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
WebViewBottomSheetContent(
|
||||
content = WebViewBottomSheetConfig(
|
||||
url = "https://tangem.com/en/",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_WebViewBottomSheetContent_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun Preview_WebViewBottomSheetContent() {
|
||||
TangemThemePreview {
|
||||
WebViewBottomSheetContent(
|
||||
content = WebViewBottomSheetConfig(
|
||||
url = "https://tangem.com/en/",
|
||||
|
|
|
|||
|
|
@ -540,6 +540,12 @@ internal class SwapViewModel @Inject constructor(
|
|||
is SwapTransactionState.UserCancelled -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
}
|
||||
is SwapTransactionState.DemoMode -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
uiState = stateBuilder.createDemoModeAlert(uiState) {
|
||||
uiState = stateBuilder.clearAlert(uiState)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
startLoadingQuotesFromLastState()
|
||||
uiState = stateBuilder.createErrorTransaction(uiState, it) {
|
||||
|
|
@ -881,12 +887,6 @@ internal class SwapViewModel @Inject constructor(
|
|||
},
|
||||
onMaxAmountSelected = ::onMaxAmountClicked,
|
||||
onReduceAmount = ::onReduceAmountClicked,
|
||||
onReduceAmountIgnoreClick = {
|
||||
uiState = uiState.copy(
|
||||
reduceAmountIgnore = true,
|
||||
warnings = uiState.warnings.filter { it !is SwapWarning.ReduceAmount },
|
||||
)
|
||||
},
|
||||
openPermissionBottomSheet = {
|
||||
singleTaskScheduler.cancelTask()
|
||||
analyticsEventHandler.send(SwapEvents.ButtonGivePermissionClicked)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.tester.api
|
||||
|
||||
/**
|
||||
* Interface for app restarter
|
||||
*/
|
||||
interface AppRestarter {
|
||||
|
||||
fun restart()
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.feature.tester
|
||||
|
||||
import android.app.Activity
|
||||
|
||||
/**
|
||||
* Wraps the main activity class to avoid type erasure issues during injection.
|
||||
*
|
||||
* @property clazz activity class
|
||||
*/
|
||||
class ActivityClassWrapper(
|
||||
val clazz: Class<out Activity>,
|
||||
)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.feature.tester.apprestarter
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.tangem.feature.tester.ActivityClassWrapper
|
||||
import com.tangem.features.tester.api.AppRestarter
|
||||
|
||||
/**
|
||||
* Entity that kills the process and restarts the main activity
|
||||
* @property context Activity context
|
||||
*/
|
||||
internal class DefaultAppRestarter(
|
||||
private val context: Context,
|
||||
private val activityClassWrapper: ActivityClassWrapper,
|
||||
) : AppRestarter {
|
||||
|
||||
override fun restart() {
|
||||
if (context !is Activity) return
|
||||
|
||||
context.finish()
|
||||
context.startActivity(
|
||||
Intent(context, activityClassWrapper.clazz).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP },
|
||||
)
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.feature.tester.di
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.feature.tester.ActivityClassWrapper
|
||||
import com.tangem.feature.tester.apprestarter.DefaultAppRestarter
|
||||
import com.tangem.features.tester.api.AppRestarter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
internal object RestarterModule {
|
||||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideAppRestarter(
|
||||
@ActivityContext context: Context,
|
||||
activityClassWrapper: ActivityClassWrapper,
|
||||
): AppRestarter {
|
||||
return DefaultAppRestarter(context, activityClassWrapper)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeActivity
|
||||
import com.tangem.core.ui.theme.AppThemeModeHolder
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsScreen
|
||||
import com.tangem.feature.tester.presentation.actions.TesterActionsViewModel
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.ui.FeatureTogglesScreen
|
||||
|
|
@ -18,6 +18,7 @@ import com.tangem.feature.tester.presentation.menu.state.TesterMenuContentState
|
|||
import com.tangem.feature.tester.presentation.menu.ui.TesterMenuScreen
|
||||
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
|
||||
import com.tangem.feature.tester.presentation.navigation.TesterScreen
|
||||
import com.tangem.features.tester.api.AppRestarter
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
|
@ -27,12 +28,15 @@ import javax.inject.Inject
|
|||
internal class TesterActivity : ComposeActivity() {
|
||||
|
||||
@Inject
|
||||
override lateinit var appThemeModeHolder: AppThemeModeHolder
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
/** Router for inner feature navigation */
|
||||
@Inject
|
||||
lateinit var testerRouter: TesterRouter
|
||||
|
||||
@Inject
|
||||
lateinit var appRestarter: AppRestarter
|
||||
|
||||
private val innerTesterRouter: InnerTesterRouter
|
||||
get() = requireNotNull(testerRouter as? InnerTesterRouter) {
|
||||
"TesterRouter must be InnerTesterRouter for tester feature"
|
||||
|
|
@ -66,7 +70,7 @@ internal class TesterActivity : ComposeActivity() {
|
|||
|
||||
composable(route = TesterScreen.FEATURE_TOGGLES.name) {
|
||||
val viewModel = hiltViewModel<FeatureTogglesViewModel>().apply {
|
||||
setupNavigation(innerTesterRouter)
|
||||
setupInteractions(innerTesterRouter, appRestarter)
|
||||
}
|
||||
|
||||
FeatureTogglesScreen(state = viewModel.uiState)
|
||||
|
|
|
|||
|
|
@ -3,15 +3,16 @@ package com.tangem.feature.tester.presentation.actions
|
|||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
|
||||
internal data class TesterActionsContentState(
|
||||
val onBackClick: () -> Unit,
|
||||
val hideAllCurrenciesConfig: HideAllCurrenciesConfig,
|
||||
val toggleAppThemeConfig: ToggleAppThemeConfig,
|
||||
val onBackClick: () -> Unit,
|
||||
val onApplyChangesClick: () -> Unit,
|
||||
)
|
||||
|
||||
internal sealed class HideAllCurrenciesConfig {
|
||||
data class Clickable(val onClick: () -> Unit) : HideAllCurrenciesConfig()
|
||||
|
||||
object Progress : HideAllCurrenciesConfig()
|
||||
data object Progress : HideAllCurrenciesConfig()
|
||||
}
|
||||
|
||||
internal data class ToggleAppThemeConfig(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.tester.presentation.actions
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -12,6 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.feature.tester.impl.R
|
||||
|
||||
|
|
@ -76,26 +78,20 @@ private fun TesterActionsScreenSample(modifier: Modifier = Modifier) {
|
|||
) {
|
||||
TesterActionsScreen(
|
||||
state = TesterActionsContentState(
|
||||
onBackClick = {},
|
||||
hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable {},
|
||||
toggleAppThemeConfig = ToggleAppThemeConfig(AppThemeMode.DEFAULT) {},
|
||||
onBackClick = {},
|
||||
onApplyChangesClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TesterActionsScreenPreview_Light() {
|
||||
TangemTheme {
|
||||
TesterActionsScreenSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun TesterActionsScreenPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun TesterActionsScreenPreview() {
|
||||
TangemThemePreview {
|
||||
TesterActionsScreenSample()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,10 @@ internal class TesterActionsViewModel @Inject constructor(
|
|||
|
||||
private val initialState: TesterActionsContentState
|
||||
get() = TesterActionsContentState(
|
||||
onBackClick = { /* no-op */ },
|
||||
hideAllCurrenciesConfig = HideAllCurrenciesConfig.Clickable(this::hideAllCurrencies),
|
||||
toggleAppThemeConfig = ToggleAppThemeConfig(AppThemeMode.DEFAULT, this::toggleAppTheme),
|
||||
onBackClick = { /* no-op */ },
|
||||
onApplyChangesClick = { /* no-op */ },
|
||||
)
|
||||
|
||||
init {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@ import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatur
|
|||
* @property featureToggles feature toggles list
|
||||
* @property onBackClick the lambda to be invoked when back button is pressed
|
||||
* @property onToggleValueChange the lambda to be invoked when switch button is pressed
|
||||
* @property onApplyChangesClick the lambda to be invoked when apply changes button is pressed
|
||||
*/
|
||||
internal data class FeatureTogglesContentState(
|
||||
val featureToggles: List<TesterFeatureToggle>,
|
||||
val onBackClick: () -> Unit,
|
||||
val onToggleValueChange: (String, Boolean) -> Unit,
|
||||
val onBackClick: () -> Unit,
|
||||
val onApplyChangesClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.tester.presentation.featuretoggles.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -18,8 +19,10 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.tester.impl.R
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState
|
||||
|
|
@ -49,6 +52,14 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) {
|
|||
onCheckedChange = { isChange -> state.onToggleValueChange(featureToggle.name, isChange) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
PrimaryButton(
|
||||
text = stringResource(id = R.string.apply_changes),
|
||||
onClick = state.onApplyChangesClick,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,26 +92,10 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo
|
|||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewFeatureTogglesScreen_InLightTheme() {
|
||||
TangemTheme(isDark = false) {
|
||||
FeatureTogglesScreen(
|
||||
state = FeatureTogglesContentState(
|
||||
featureToggles = listOf(
|
||||
TesterFeatureToggle(name = "FEATURE_TOGGLE_1", isEnabled = true),
|
||||
TesterFeatureToggle(name = "FEATURE_TOGGLE_2", isEnabled = false),
|
||||
),
|
||||
onToggleValueChange = { _, _ -> },
|
||||
onBackClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewFeatureTogglesScreen_InDarkTheme() {
|
||||
TangemTheme(isDark = true) {
|
||||
private fun PreviewFeatureTogglesScreen() {
|
||||
TangemThemePreview {
|
||||
FeatureTogglesScreen(
|
||||
state = FeatureTogglesContentState(
|
||||
featureToggles = listOf(
|
||||
|
|
@ -109,6 +104,7 @@ private fun PreviewFeatureTogglesScreen_InDarkTheme() {
|
|||
),
|
||||
onToggleValueChange = { _, _ -> },
|
||||
onBackClick = {},
|
||||
onApplyChangesClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.featuretoggle.manager.MutableFeatureTogglesManager
|
|||
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
|
||||
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState
|
||||
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
|
||||
import com.tangem.features.tester.api.AppRestarter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -38,16 +39,20 @@ internal class FeatureTogglesViewModel @Inject constructor(
|
|||
"Feature toggle manager must be mutable (debug build type)"
|
||||
}
|
||||
|
||||
/** Setup navigation state property by router [router] */
|
||||
fun setupNavigation(router: InnerTesterRouter) {
|
||||
uiState = uiState.copy(onBackClick = router::back)
|
||||
/** Setup navigation state property by router [router] and provides app restart method by [appRestarter] */
|
||||
fun setupInteractions(router: InnerTesterRouter, appRestarter: AppRestarter) {
|
||||
uiState = uiState.copy(
|
||||
onBackClick = router::back,
|
||||
onApplyChangesClick = appRestarter::restart,
|
||||
)
|
||||
}
|
||||
|
||||
private fun initState(): FeatureTogglesContentState {
|
||||
return FeatureTogglesContentState(
|
||||
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
|
||||
onBackClick = {},
|
||||
onToggleValueChange = ::onToggleValueChange,
|
||||
onBackClick = {},
|
||||
onApplyChangesClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue