Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-11 23:26:10 +04:00
parent 36a3e2f7d6
commit 0cc270adc0
14 changed files with 464 additions and 1 deletions

View file

@ -22,6 +22,7 @@ import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.shop.ui.ShopFragment
import com.tangem.tap.features.tokens.addCustomToken.AddCustomTokenFragment
@ -112,5 +113,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.QrScan -> QrScanFragment()
AppScreen.ReferralProgram -> ReferralFragment()
AppScreen.Welcome -> WelcomeFragment()
AppScreen.SaveWallet -> SaveWalletBottomSheetFragment()
}
}

View file

@ -25,6 +25,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayMiddleware
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.shop.redux.ShopMiddleware
@ -53,6 +54,7 @@ data class AppState(
val walletConnectState: WalletConnectState = WalletConnectState(),
val shopState: ShopState = ShopState(),
val welcomeState: WelcomeState = WelcomeState(),
val saveWalletState: SaveWalletState = SaveWalletState(),
) : StateType {
val domainState: DomainState

View file

@ -23,5 +23,6 @@ enum class AppScreen(
WalletConnectSessions,
QrScan,
ReferralProgram,
Welcome
Welcome,
SaveWallet(isDialogFragment = true),
}

View file

@ -0,0 +1,17 @@
package com.tangem.tap.features.saveWallet.redux
import com.tangem.common.core.TangemError
import org.rekotlin.Action
internal sealed interface SaveWalletAction : Action {
object Save : SaveWalletAction {
object Success : SaveWalletAction
data class Error(val error: TangemError) : SaveWalletAction
}
object Dismiss : SaveWalletAction
object CloseError : SaveWalletAction
object SaveWalletWasShown : SaveWalletAction
}

View file

@ -0,0 +1,5 @@
package com.tangem.tap.features.saveWallet.redux
import org.rekotlin.StateType
class SaveWalletState : StateType

View file

@ -0,0 +1,83 @@
package com.tangem.tap.features.saveWallet.ui
import android.content.DialogInterface
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
import com.tangem.core.ui.models.EnrollBiometricsDialog
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent
internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWalletScreenState>() {
override val expandedHeightFraction: Float = .95f
private val viewModel by viewModels<SaveWalletViewModel>()
override fun onDismiss(dialog: DialogInterface) {
viewModel.dismiss()
super.onDismiss(dialog)
}
@Composable
override fun provideState(): State<SaveWalletScreenState> {
return viewModel.state.collectAsState()
}
@Composable
override fun ScreenContent(
modifier: Modifier,
state: SaveWalletScreenState,
) {
val snackbarHostState = remember { SnackbarHostState() }
val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage)
val enrollBiometricsDialog by rememberUpdatedState(newValue = state.enrollBiometricsDialog)
Box(modifier = modifier) {
SaveWalletScreenContent(
showProgress = state.showProgress,
onSaveWalletClick = viewModel::saveWallet,
onCloseClick = this@SaveWalletBottomSheetFragment::dismissAllowingStateLoss,
)
SnackbarHost(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(vertical = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
hostState = snackbarHostState,
)
}
EnrollBiometricsDialog(dialog = enrollBiometricsDialog)
LaunchedEffect(key1 = errorMessage) {
errorMessage?.let {
snackbarHostState.showSnackbar(it)
viewModel.closeError()
}
}
}
@Composable
private fun EnrollBiometricsDialog(
modifier: Modifier = Modifier,
dialog: EnrollBiometricsDialog?,
) {
if (dialog == null) return
EnrollBiometricsDialogContent(modifier, dialog)
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.tap.features.saveWallet.ui
import androidx.compose.runtime.Immutable
import com.tangem.common.core.TangemError
import com.tangem.core.ui.models.EnrollBiometricsDialog
@Immutable
internal data class SaveWalletScreenState(
val showProgress: Boolean = false,
val enrollBiometricsDialog: EnrollBiometricsDialog? = null,
val error: TangemError? = null,
)

View file

@ -0,0 +1,51 @@
package com.tangem.tap.features.saveWallet.ui
import androidx.lifecycle.ViewModel
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
import com.tangem.tap.store
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import org.rekotlin.StoreSubscriber
internal class SaveWalletViewModel : ViewModel(), StoreSubscriber<SaveWalletState> {
private val stateInternal = MutableStateFlow(SaveWalletScreenState())
val state: StateFlow<SaveWalletScreenState> = stateInternal
init {
subscribeToStoreChanges()
store.dispatchOnMain(SaveWalletAction.SaveWalletWasShown)
}
fun saveWallet() {
store.dispatch(SaveWalletAction.Save)
}
fun dismiss() {
store.dispatch(SaveWalletAction.Dismiss)
}
fun closeError() {
store.dispatch(SaveWalletAction.CloseError)
}
override fun newState(state: SaveWalletState) {
stateInternal.update { prevState ->
// TODO: Update screen state
prevState
}
}
override fun onCleared() {
store.unsubscribe(this)
}
private fun subscribeToStoreChanges() {
store.subscribe(this) { appState ->
appState.skip { old, new -> old.saveWalletState == new.saveWalletState }
.select { it.saveWalletState }
}
}
}

View file

@ -0,0 +1,157 @@
package com.tangem.tap.features.saveWallet.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.res.TangemTheme
import com.tangem.wallet.R
@Composable
internal fun SaveWalletScreenContent(
modifier: Modifier = Modifier,
showProgress: Boolean,
onSaveWalletClick: () -> Unit,
onCloseClick: () -> Unit,
) {
Column(
modifier = modifier
.padding(horizontal = TangemTheme.dimens.spacing16),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Hand()
IconButton(
modifier = Modifier
.align(Alignment.End)
.padding(all = TangemTheme.dimens.spacing8)
.size(TangemTheme.dimens.size32),
onClick = onCloseClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_close),
tint = TangemTheme.colors.icon.secondary,
contentDescription = stringResource(id = R.string.common_cancel),
)
}
Spacer(modifier = Modifier.weight(.7f))
Icon(
modifier = Modifier.size(TangemTheme.dimens.size56),
painter = painterResource(id = R.drawable.ic_fingerprint_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
SpacerH24()
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.text.accent.copy(alpha = .12f),
shape = TangemTheme.shapes.roundedCornersMedium,
),
) {
Text(
modifier = Modifier
.padding(
vertical = TangemTheme.dimens.spacing4,
horizontal = TangemTheme.dimens.spacing12,
),
text = stringResource(R.string.save_user_wallet_agreement_new_feature),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.accent,
textAlign = TextAlign.Center,
)
}
SpacerH24()
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.save_user_wallet_agreement_header),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
)
SpacerH12()
Text(
modifier = Modifier.fillMaxWidth(fraction = .87f),
text = stringResource(R.string.save_user_wallet_agreement_description),
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.weight(1f))
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
showProgress = showProgress,
text = stringResource(R.string.save_user_wallet_agreement_allow),
onClick = onSaveWalletClick,
)
SpacerH16()
Text(
modifier = Modifier.fillMaxWidth(fraction = .7f),
text = stringResource(R.string.save_user_wallet_agreement_notice),
style = TangemTheme.typography.caption,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
SpacerH16()
}
}
// region Preview
@Composable
private fun SaveWalletScreenContentSample(
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.background(TangemTheme.colors.background.primary),
) {
SaveWalletScreenContent(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing32)
.fillMaxSize()
.background(
color = TangemTheme.colors.background.plain,
shape = TangemTheme.shapes.bottomSheet,
),
showProgress = false,
onSaveWalletClick = { /* no-op */ },
onCloseClick = { /* no-op */ },
)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun SaveWalletScreenContentPreview_Light() {
TangemTheme {
SaveWalletScreenContentSample()
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
private fun SaveWalletScreenContentPreview_Dark() {
TangemTheme(isDark = true) {
SaveWalletScreenContentSample()
}
}
// endregion Preview