diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 911eededb2..0f1f035a13 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -1,5 +1,6 @@ package com.tangem.tap.di.domain +import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.transaction.WalletAddressServiceRepository @@ -9,8 +10,8 @@ import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate +import com.tangem.domain.wallets.hot.HotWalletAccessor import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.core.wallets.UserWalletsListRepository import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.* @@ -372,4 +373,32 @@ internal object WalletsDomainModule { walletsRepository = walletsRepository, ) } + + @Provides + @Singleton + fun providesUnlockHotWalletContextualUseCase( + hotWalletAccessor: HotWalletAccessor, + ): UnlockHotWalletContextualUseCase { + return UnlockHotWalletContextualUseCase( + hotWalletAccessor = hotWalletAccessor, + ) + } + + @Provides + @Singleton + fun providesClearHotWalletContextualUnlockUseCase( + hotWalletAccessor: HotWalletAccessor, + ): ClearHotWalletContextualUnlockUseCase { + return ClearHotWalletContextualUnlockUseCase( + hotWalletAccessor = hotWalletAccessor, + ) + } + + @Provides + @Singleton + fun providesExportSeedPhraseUseCase(hotWalletAccessor: HotWalletAccessor): ExportSeedPhraseUseCase { + return ExportSeedPhraseUseCase( + hotWalletAccessor = hotWalletAccessor, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/hot/TangemHotSDKProxy.kt b/app/src/main/java/com/tangem/tap/features/hot/TangemHotSDKProxy.kt index af00ff8af8..237bc5ebd3 100644 --- a/app/src/main/java/com/tangem/tap/features/hot/TangemHotSDKProxy.kt +++ b/app/src/main/java/com/tangem/tap/features/hot/TangemHotSDKProxy.kt @@ -32,6 +32,13 @@ class TangemHotSDKProxy @Inject constructor() : TangemHotSdk { override suspend fun exportBackup(unlockHotWallet: UnlockHotWallet): ByteArray = callSdk { exportBackup(unlockHotWallet) } + override suspend fun clearUnlockContext(hotWalletId: HotWalletId) { + callSdk { clearUnlockContext(hotWalletId) } + } + + override suspend fun getContextUnlock(unlockHotWallet: UnlockHotWallet): UnlockHotWallet = + callSdk { getContextUnlock(unlockHotWallet) } + override suspend fun delete(id: HotWalletId) = callSdk { delete(id) } override suspend fun changeAuth(unlockHotWallet: UnlockHotWallet, auth: HotAuth): HotWalletId = diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 5276185422..9525c29fa0 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -23,6 +23,7 @@ import com.tangem.features.hotwallet.CreateWalletBackupComponent import com.tangem.features.hotwallet.UpdateAccessCodeComponent import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.features.hotwallet.WalletBackupComponent +import com.tangem.features.hotwallet.ViewPhraseComponent import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource @@ -109,6 +110,7 @@ internal class ChildFactory @Inject constructor( private val walletActivationComponentFactory: WalletActivationComponent.Factory, private val createWalletBackupComponentFactory: CreateWalletBackupComponent.Factory, private val updateAccessCodeComponentFactory: UpdateAccessCodeComponent.Factory, + private val viewPhraseComponentFactory: ViewPhraseComponent.Factory, private val sendWithSwapComponentFactory: SendWithSwapComponent.Factory, private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory, private val tangemPayDetailsComponentFactory: TangemPayDetailsComponent.Factory, @@ -532,6 +534,15 @@ internal class ChildFactory @Inject constructor( componentFactory = updateAccessCodeComponentFactory, ) } + is AppRoute.ViewPhrase -> { + createComponentChild( + context = context, + params = ViewPhraseComponent.Params( + userWalletId = route.userWalletId, + ), + componentFactory = viewPhraseComponentFactory, + ) + } is AppRoute.SendEntryPoint -> { createComponentChild( context = context, diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 7e98053f5f..06cede4e5a 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -334,6 +334,11 @@ sealed class AppRoute(val path: String) : Route { val userWalletId: UserWalletId, ) : AppRoute(path = "/update_access_code/${userWalletId.stringValue}") + @Serializable + data class ViewPhrase( + val userWalletId: UserWalletId, + ) : AppRoute(path = "/view_seed_phrase/${userWalletId.stringValue}") + @Serializable data class SendEntryPoint( val userWalletId: UserWalletId, diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt index 796bc8d3ba..70273f2684 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotWalletAccessor.kt @@ -9,15 +9,26 @@ import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.hot.sdk.TangemHotSdk import com.tangem.hot.sdk.exception.WrongPasswordException import com.tangem.hot.sdk.model.* +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject +import kotlin.collections.set class DefaultHotWalletAccessor @Inject constructor( private val tangemHotSdk: TangemHotSdk, private val userWalletsListRepository: UserWalletsListRepository, private val hotWalletPasswordRequester: HotWalletPasswordRequester, private val walletsRepository: WalletsRepository, + dispatchers: CoroutineDispatcherProvider, ) : HotWalletAccessor { + private val scope = CoroutineScope(context = SupervisorJob() + dispatchers.io) + + private var contextualUnlockHotWallet: ConcurrentHashMap = ConcurrentHashMap() + override suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List): List = hotSdkRequest(hotWalletId) { unlock -> tangemHotSdk.signHashes(unlockHotWallet = unlock, dataToSign = dataToSign) @@ -30,6 +41,24 @@ class DefaultHotWalletAccessor @Inject constructor( tangemHotSdk.derivePublicKey(unlockHotWallet = unlock, request = request) } + override suspend fun exportSeedPhrase(hotWalletId: HotWalletId): SeedPhrasePrivateInfo { + val unlockHotWallet = contextualUnlockHotWallet[hotWalletId] ?: hotSdkRequest(hotWalletId) { it } + return tangemHotSdk.exportMnemonic(unlockHotWallet = unlockHotWallet) + } + + override suspend fun unlockContextual(hotWalletId: HotWalletId): UnlockHotWallet = hotSdkRequest(hotWalletId) { + tangemHotSdk.getContextUnlock(it).also { unlockHotWallet -> + contextualUnlockHotWallet[hotWalletId] = unlockHotWallet + } + } + + override fun clearContextualUnlock(hotWalletId: HotWalletId) { + contextualUnlockHotWallet[hotWalletId] = null + scope.launch { + tangemHotSdk.clearUnlockContext(hotWalletId) + } + } + private suspend fun hotSdkRequest(hotWalletId: HotWalletId, block: suspend (unlock: UnlockHotWallet) -> T): T { val isAccessCodeRequired = walletsRepository.requireAccessCode() diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/hot/HotWalletAccessor.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/hot/HotWalletAccessor.kt index 18d9d792fe..f0297ced18 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/hot/HotWalletAccessor.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/hot/HotWalletAccessor.kt @@ -7,4 +7,10 @@ interface HotWalletAccessor { suspend fun signHashes(hotWalletId: HotWalletId, dataToSign: List): List suspend fun derivePublicKeys(hotWalletId: HotWalletId, request: DeriveWalletRequest): DerivedPublicKeyResponse + + suspend fun exportSeedPhrase(hotWalletId: HotWalletId): SeedPhrasePrivateInfo + + suspend fun unlockContextual(hotWalletId: HotWalletId): UnlockHotWallet + + fun clearContextualUnlock(hotWalletId: HotWalletId) } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ClearHotWalletContextualUnlockUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ClearHotWalletContextualUnlockUseCase.kt new file mode 100644 index 0000000000..be1a030837 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ClearHotWalletContextualUnlockUseCase.kt @@ -0,0 +1,17 @@ + +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import com.tangem.domain.wallets.hot.HotWalletAccessor +import com.tangem.hot.sdk.model.HotWalletId + +class ClearHotWalletContextualUnlockUseCase( + private val hotWalletAccessor: HotWalletAccessor, +) { + + operator fun invoke(hotWalletId: HotWalletId): Either { + return Either.catch { + hotWalletAccessor.clearContextualUnlock(hotWalletId) + } + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ExportSeedPhraseUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ExportSeedPhraseUseCase.kt new file mode 100644 index 0000000000..465a2560c3 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/ExportSeedPhraseUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import com.tangem.domain.wallets.hot.HotWalletAccessor +import com.tangem.hot.sdk.model.HotWalletId +import com.tangem.hot.sdk.model.SeedPhrasePrivateInfo + +class ExportSeedPhraseUseCase( + private val hotWalletAccessor: HotWalletAccessor, +) { + + suspend operator fun invoke(hotWalletId: HotWalletId): Either { + return Either.catch { + hotWalletAccessor.exportSeedPhrase(hotWalletId) + } + } +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockHotWalletContextualUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockHotWalletContextualUseCase.kt new file mode 100644 index 0000000000..982821ce5b --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/UnlockHotWalletContextualUseCase.kt @@ -0,0 +1,18 @@ + +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import com.tangem.domain.wallets.hot.HotWalletAccessor +import com.tangem.hot.sdk.model.HotWalletId +import com.tangem.hot.sdk.model.UnlockHotWallet + +class UnlockHotWalletContextualUseCase( + private val hotWalletAccessor: HotWalletAccessor, +) { + + suspend operator fun invoke(hotWalletId: HotWalletId): Either { + return Either.catch { + hotWalletAccessor.unlockContextual(hotWalletId) + } + } +} \ No newline at end of file diff --git a/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/ui/CreateWalletSelectionContent.kt b/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/ui/CreateWalletSelectionContent.kt index 4106c3d8c6..abae1d96ad 100644 --- a/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/ui/CreateWalletSelectionContent.kt +++ b/features/create-wallet-selection/impl/src/main/kotlin/com/tangem/features/createwalletselection/ui/CreateWalletSelectionContent.kt @@ -149,7 +149,7 @@ private fun WalletBlock( .padding(top = 8.dp) .clip(TangemTheme.shapes.roundedCornersXMedium) .background( - color = TangemTheme.colors.background.secondary, + color = TangemTheme.colors.field.primary, shape = TangemTheme.shapes.roundedCornersXMedium, ) .clickable(onClick = onClick) diff --git a/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/ViewPhraseComponent.kt b/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/ViewPhraseComponent.kt new file mode 100644 index 0000000000..99e7dcf23f --- /dev/null +++ b/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/ViewPhraseComponent.kt @@ -0,0 +1,14 @@ +package com.tangem.features.hotwallet + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.wallet.UserWalletId + +interface ViewPhraseComponent : ComposableContentComponent { + + data class Params( + val userWalletId: UserWalletId, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/DefaultViewPhraseComponent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/DefaultViewPhraseComponent.kt new file mode 100644 index 0000000000..d82fbd968e --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/DefaultViewPhraseComponent.kt @@ -0,0 +1,38 @@ +package com.tangem.features.hotwallet.viewphrase + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.hotwallet.ViewPhraseComponent +import com.tangem.features.hotwallet.viewphrase.model.ViewPhraseModel +import com.tangem.features.hotwallet.viewphrase.ui.ViewPhraseContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultViewPhraseComponent @AssistedInject constructor( + @Assisted private val context: AppComponentContext, + @Assisted private val params: ViewPhraseComponent.Params, +) : ViewPhraseComponent, AppComponentContext by context { + private val model: ViewPhraseModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + ViewPhraseContent( + state = state, + modifier = modifier, + ) + } + + @AssistedFactory + interface Factory : ViewPhraseComponent.Factory { + override fun create( + context: AppComponentContext, + params: ViewPhraseComponent.Params, + ): DefaultViewPhraseComponent + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/di/ViewPhraseModule.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/di/ViewPhraseModule.kt new file mode 100644 index 0000000000..e6b6d7d4ab --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/di/ViewPhraseModule.kt @@ -0,0 +1,25 @@ +package com.tangem.features.hotwallet.viewphrase.di + +import com.tangem.core.decompose.model.Model +import com.tangem.features.hotwallet.ViewPhraseComponent +import com.tangem.features.hotwallet.viewphrase.DefaultViewPhraseComponent +import com.tangem.features.hotwallet.viewphrase.model.ViewPhraseModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface ViewPhraseModule { + + @Binds + @IntoMap + @ClassKey(ViewPhraseModel::class) + fun bindViewPhraseModel(model: ViewPhraseModel): Model + + @Binds + fun bindViewPhraseComponentFactory(factory: DefaultViewPhraseComponent.Factory): ViewPhraseComponent.Factory +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/entity/ViewPhraseUM.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/entity/ViewPhraseUM.kt new file mode 100644 index 0000000000..072bed311d --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/entity/ViewPhraseUM.kt @@ -0,0 +1,10 @@ +package com.tangem.features.hotwallet.viewphrase.entity + +import com.tangem.core.ui.components.grid.entity.EnumeratedTwoColumnGridItem +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +internal data class ViewPhraseUM( + val onBackClick: () -> Unit, + val words: ImmutableList = persistentListOf(), +) \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/model/ViewPhraseModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/model/ViewPhraseModel.kt new file mode 100644 index 0000000000..f2d3adf53e --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/model/ViewPhraseModel.kt @@ -0,0 +1,76 @@ +package com.tangem.features.hotwallet.viewphrase.model + +import androidx.compose.runtime.Stable +import arrow.core.getOrElse +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.ui.components.grid.entity.EnumeratedTwoColumnGridItem +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.usecase.ClearHotWalletContextualUnlockUseCase +import com.tangem.domain.wallets.usecase.ExportSeedPhraseUseCase +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.features.hotwallet.ViewPhraseComponent +import com.tangem.features.hotwallet.viewphrase.entity.ViewPhraseUM +import com.tangem.hot.sdk.model.HotWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Stable +@ModelScoped +internal class ViewPhraseModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val exportSeedPhraseUseCase: ExportSeedPhraseUseCase, + private val clearHotWalletContextualUnlockUseCase: ClearHotWalletContextualUnlockUseCase, +) : Model() { + + private val params = paramsContainer.require() + + private var hotWalletId: HotWalletId? = null + + internal val uiState: StateFlow + field = MutableStateFlow( + ViewPhraseUM( + onBackClick = { router.pop() }, + ), + ) + + init { + loadSeedPhrase() + } + + private fun loadSeedPhrase() { + val userWallet = getUserWalletUseCase(params.userWalletId) + .getOrElse { error("User wallet with id ${params.userWalletId} not found") } + if (userWallet is UserWallet.Hot) { + hotWalletId = userWallet.hotWalletId + modelScope.launch { + val words = exportSeedPhraseUseCase.invoke(userWallet.hotWalletId) + .getOrElse { error("Unable to export seed phrase for wallet with id ${params.userWalletId}") } + .mnemonic + .mnemonicComponents + uiState.update { + it.copy( + words = words.mapIndexed { index, s -> + EnumeratedTwoColumnGridItem(index + 1, s) + }.toImmutableList(), + ) + } + } + } + } + + override fun onDestroy() { + hotWalletId?.let { clearHotWalletContextualUnlockUseCase.invoke(it) } + super.onDestroy() + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/ui/ViewPhraseContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/ui/ViewPhraseContent.kt new file mode 100644 index 0000000000..8768e09a55 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/ui/ViewPhraseContent.kt @@ -0,0 +1,103 @@ +package com.tangem.features.hotwallet.viewphrase.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.components.grid.EnumeratedTwoColumnGrid +import com.tangem.core.ui.components.grid.entity.EnumeratedTwoColumnGridItem +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.hotwallet.impl.R +import com.tangem.features.hotwallet.viewphrase.entity.ViewPhraseUM +import kotlinx.collections.immutable.toImmutableList + +@Composable +internal fun ViewPhraseContent(state: ViewPhraseUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary) + .fillMaxSize() + .systemBarsPadding(), + ) { + AppBarWithBackButton( + text = stringResourceSafe(R.string.common_backup), + onBackClick = state.onBackClick, + ) + + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .imePadding() + .weight(1f), + ) { + TitleBlock( + state = state, + modifier = Modifier.padding(top = 20.dp), + ) + + EnumeratedTwoColumnGrid( + items = state.words, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp, bottom = 32.dp), + ) + } + } +} + +@Composable +private fun TitleBlock(state: ViewPhraseUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.size36) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = stringResourceSafe(R.string.backup_seed_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + Text( + text = stringResourceSafe( + R.string.backup_seed_caution, + state.words.size, + ), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Preview(showBackground = true, widthDp = 360, heightDp = 640) +@Preview(showBackground = true, widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview() { + TangemThemePreview { + ViewPhraseContent( + state = ViewPhraseUM( + onBackClick = {}, + words = List(12) { + EnumeratedTwoColumnGridItem( + index = it + 1, + mnemonic = "word${it + 1}", + ) + }.toImmutableList(), + ), + ) + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModel.kt index 20c094ed28..b7357044d5 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModel.kt @@ -1,38 +1,33 @@ package com.tangem.features.hotwallet.walletbackup.model -import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router -import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.R -import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2 -import com.tangem.core.ui.components.bottomsheets.message.icon -import com.tangem.core.ui.components.bottomsheets.message.infoBlock -import com.tangem.core.ui.components.bottomsheets.message.onClick -import com.tangem.core.ui.components.bottomsheets.message.secondaryButton import com.tangem.core.ui.components.label.entity.LabelStyle import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.message.bottomSheetMessage import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.common.routing.AppRoute +import com.tangem.domain.wallets.usecase.UnlockHotWalletContextualUseCase import com.tangem.features.hotwallet.WalletBackupComponent import com.tangem.features.hotwallet.walletbackup.entity.BackupStatus import com.tangem.features.hotwallet.walletbackup.entity.WalletBackupUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @ModelScoped internal class WalletBackupModel @Inject constructor( paramsContainer: ParamsContainer, - getWalletUseCase: GetUserWalletUseCase, - private val router: Router, + private val getWalletUseCase: GetUserWalletUseCase, + private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase, override val dispatchers: CoroutineDispatcherProvider, - @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, + private val router: Router, ) : Model() { private val params: WalletBackupComponent.Params = paramsContainer.require() @@ -57,34 +52,16 @@ internal class WalletBackupModel @Inject constructor( ), ) - private val makeBackupAtFirstAlertBS - get() = bottomSheetMessage { - infoBlock { - icon(R.drawable.ic_passcode_lock_32) { - type = MessageBottomSheetUMV2.Icon.Type.Accent - backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.SameAsTint - } - title = resourceReference(R.string.hw_backup_need_title) - body = resourceReference(R.string.hw_backup_need_description) - } - secondaryButton { - text = resourceReference(R.string.hw_backup_need_action) - onClick { - router.push(AppRoute.CreateWalletBackup(params.userWalletId)) - closeBs() - } - } - } - init { - getWalletUseCase.invokeFlow(params.userWalletId) - .map { it.getOrNull() } - .distinctUntilChanged() - .filterNotNull() - .onEach { - updateBackupStatuses(it) - } - .launchIn(modelScope) + getWalletUseCase.invoke(params.userWalletId) + .fold( + ifLeft = { + Timber.e("Error on getting user wallet: $it") + }, + ifRight = { + updateBackupStatuses(it) + }, + ) } private fun updateBackupStatuses(userWallet: UserWallet) { @@ -118,9 +95,37 @@ internal class WalletBackupModel @Inject constructor( private fun onRecoveryPhraseClick() { if (uiState.value.backedUp) { - // TODO [REDACTED_TASK_KEY] + getWalletUseCase.invoke(params.userWalletId) + .fold( + ifLeft = { + Timber.e("Error on getting user wallet: $it") + }, + ifRight = { userWallet -> + when (userWallet) { + is UserWallet.Cold -> { + val userWalletId = userWallet.walletId + Timber.e("Unexpected cold wallet when request seed phrase: $userWalletId") + } + is UserWallet.Hot -> showSeedPhrase(userWallet) + } + }, + ) } else { - uiMessageSender.send(makeBackupAtFirstAlertBS) + router.push(AppRoute.CreateWalletBackup(params.userWalletId)) + } + } + + private fun showSeedPhrase(hotWallet: UserWallet.Hot) { + modelScope.launch { + unlockHotWalletContextualUseCase.invoke(hotWallet.hotWalletId) + .fold( + ifLeft = { + Timber.e("Error while export seed phrase: $it") + }, + ifRight = { seedPhrasePrivateInfo -> + router.push(AppRoute.ViewPhrase(params.userWalletId)) + }, + ) } } diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 72c784bae5..80bdb6d116 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -11,7 +11,7 @@ tangemCardSdk = "develop-561" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ -tangemHotSdk = "develop-525" +tangemHotSdk = "develop-526" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^