Updated on 2026-08-14
This commit is contained in:
commit
2f84def7cc
1244 changed files with 20573 additions and 7307 deletions
|
|
@ -25,7 +25,7 @@ dependencies {
|
|||
|
||||
/** Domain */
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
api(projects.domain.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.card)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.settings.SetSaveWalletScreenShownUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.biometry.AskBiometryComponent
|
||||
import com.tangem.features.biometry.impl.ui.state.AskBiometryUM
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ private fun WalletBlock(
|
|||
Row {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.weight(1f, fill = false)
|
||||
.padding(end = 8.dp),
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ android {
|
|||
dependencies {
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@ package com.tangem.features.details.component
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface DetailsComponent : ComposableContentComponent {
|
||||
|
||||
interface Factory : ComponentFactory<Params, DetailsComponent>
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
data class Params(val userWalletId: UserWalletId)
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import androidx.compose.ui.Modifier
|
|||
import com.tangem.core.decompose.navigation.DummyRouter
|
||||
import com.tangem.core.navigation.url.DummyUrlOpener
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.entity.DetailsUM
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.impl.R
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import com.tangem.domain.feedback.repository.FeedbackFeatureToggles
|
|||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TestTags
|
||||
import com.tangem.core.ui.test.DetailsScreenTestTags
|
||||
import com.tangem.features.details.component.preview.PreviewDetailsComponent
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
|
|
@ -67,7 +67,7 @@ private fun Content(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.testTag(TestTags.DETAILS_SCREEN),
|
||||
modifier = modifier.testTag(DetailsScreenTestTags.SCREEN_CONTAINER),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
contentPadding = PaddingValues(
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
|
|
@ -120,7 +120,7 @@ private fun Block(
|
|||
) {
|
||||
val itemModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(TestTags.DETAILS_SCREEN_ITEM)
|
||||
.testTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
|
||||
when (model) {
|
||||
is DetailsItemUM.Basic -> {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.components.block.model.BlockUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
import com.tangem.features.details.impl.BuildConfig
|
||||
import com.tangem.features.details.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -76,17 +74,6 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) {
|
|||
onClick = { router.push(AppRoute.AppSettings) },
|
||||
),
|
||||
).let(::add)
|
||||
|
||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "tester_menu",
|
||||
block = BlockUM(
|
||||
text = stringReference(value = "Tester menu"),
|
||||
iconRes = R.drawable.ic_alert_24,
|
||||
onClick = { router.push(AppRoute.TesterMenu) },
|
||||
),
|
||||
).let(::add)
|
||||
}
|
||||
}.toImmutableList(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.SaveWalletError
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase
|
||||
import com.tangem.features.details.impl.R
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
|
|||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.TestTags.DISCLAIMER_SCREEN_ACCEPT_BUTTON
|
||||
import com.tangem.core.ui.test.TestTags.DISCLAIMER_SCREEN_CONTAINER
|
||||
import com.tangem.core.ui.test.DisclaimerScreenTestTags
|
||||
import com.tangem.core.ui.webview.applySafeSettings
|
||||
import com.tangem.features.disclaimer.impl.R
|
||||
import com.tangem.features.disclaimer.impl.entity.DisclaimerUM
|
||||
|
|
@ -58,7 +57,7 @@ internal fun DisclaimerScreen(state: DisclaimerUM) {
|
|||
modifier = Modifier
|
||||
.background(backgroundColor)
|
||||
.statusBarsPadding()
|
||||
.testTag(DISCLAIMER_SCREEN_CONTAINER),
|
||||
.testTag(DisclaimerScreenTestTags.SCREEN_CONTAINER),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -183,7 +182,7 @@ private fun BoxScope.DisclaimerButton(onAccept: (Boolean) -> Unit) {
|
|||
disabledContentColor = TangemColorPalette.Dark6,
|
||||
),
|
||||
modifier = Modifier
|
||||
.testTag(DISCLAIMER_SCREEN_ACCEPT_BUTTON)
|
||||
.testTag(DisclaimerScreenTestTags.ACCEPT_BUTTON)
|
||||
.align(Alignment.BottomCenter)
|
||||
.navigationBarsPadding()
|
||||
.padding(
|
||||
|
|
|
|||
|
|
@ -12,11 +12,18 @@ dependencies {
|
|||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Tangem libraries */
|
||||
implementation(tangemDeps.card.core)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
|
||||
/* Tangem libs */
|
||||
implementation(tangemDeps.hot.core)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.hotwallet
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface HotAccessCodeRequestComponent : ComposableContentComponent, HotWalletPasswordRequester {
|
||||
|
||||
interface Factory : ComponentFactory<Unit, HotAccessCodeRequestComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.hotwallet
|
||||
|
||||
import com.tangem.hot.sdk.model.HotAuth
|
||||
|
||||
interface HotWalletPasswordRequester {
|
||||
|
||||
suspend fun wrongPassword()
|
||||
|
||||
suspend fun requestPassword(hasBiometry: Boolean): Result
|
||||
|
||||
suspend fun dismiss()
|
||||
|
||||
sealed class Result {
|
||||
data object UseBiometry : Result()
|
||||
data object Dismiss : Result()
|
||||
data class EnteredPassword(val password: HotAuth.Password) : Result()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.hotwallet
|
||||
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
|
||||
interface MnemonicRepository {
|
||||
|
||||
val words: Set<String>
|
||||
|
||||
fun generateMnemonic(type: MnemonicType = MnemonicType.Words12): Mnemonic
|
||||
|
||||
fun generateMnemonic(mnemonicString: String): Mnemonic
|
||||
|
||||
enum class MnemonicType {
|
||||
Words12, Words24,
|
||||
}
|
||||
}
|
||||
|
|
@ -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 WalletBackupComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, WalletBackupComponent>
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ android {
|
|||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.hotWallet.api)
|
||||
implementation(projects.features.pushNotifications.api)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.configToggles)
|
||||
|
|
@ -26,6 +27,12 @@ dependencies {
|
|||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -36,6 +43,8 @@ dependencies {
|
|||
implementation(tangemDeps.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
implementation(tangemDeps.hot.core)
|
||||
implementation(tangemDeps.hot.android)
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
package com.tangem.features.hotwallet.accesscoderequest
|
||||
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.runtime.*
|
||||
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.core.ui.components.FullScreen
|
||||
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
|
||||
import com.tangem.features.hotwallet.HotWalletPasswordRequester
|
||||
import com.tangem.features.hotwallet.accesscoderequest.ui.HotAccessCodeRequestFullScreenContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
internal class DefaultHotAccessCodeRequestComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: Unit,
|
||||
) : AppComponentContext by appComponentContext, HotAccessCodeRequestComponent {
|
||||
|
||||
private val model: HotAccessCodeRequestModel = getOrCreateModel(params)
|
||||
|
||||
override suspend fun wrongPassword() {
|
||||
model.wrongAccessCode()
|
||||
}
|
||||
|
||||
override suspend fun requestPassword(hasBiometry: Boolean): HotWalletPasswordRequester.Result {
|
||||
model.show(hasBiometry)
|
||||
return model.waitResult()
|
||||
}
|
||||
|
||||
override suspend fun dismiss() {
|
||||
model.dismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
var isShownProxy by remember { mutableStateOf(state.isShown) }
|
||||
var isShownIfProxy by remember { mutableStateOf(state.isShown) }
|
||||
|
||||
if (isShownIfProxy) {
|
||||
FullScreen(focusable = true, onBackClick = state.onDismiss) {
|
||||
HotAccessCodeRequestFullScreenContent(
|
||||
state = state.copy(isShown = isShownProxy),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(state.isShown) {
|
||||
if (state.isShown) {
|
||||
isShownIfProxy = true
|
||||
delay(timeMillis = 100)
|
||||
isShownProxy = true
|
||||
} else {
|
||||
isShownProxy = false
|
||||
delay(timeMillis = 250)
|
||||
isShownIfProxy = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : HotAccessCodeRequestComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: Unit): DefaultHotAccessCodeRequestComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.features.hotwallet.accesscoderequest
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.hotwallet.HotWalletPasswordRequester
|
||||
import com.tangem.features.hotwallet.accesscoderequest.entity.HotAccessCodeRequestUM
|
||||
import com.tangem.hot.sdk.model.HotAuth
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class HotAccessCodeRequestModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val result = MutableStateFlow<HotWalletPasswordRequester.Result?>(null)
|
||||
|
||||
val uiState: StateFlow<HotAccessCodeRequestUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
fun dismiss() {
|
||||
result.value = HotWalletPasswordRequester.Result.Dismiss
|
||||
dismissState()
|
||||
}
|
||||
|
||||
fun show(hasBiometry: Boolean) {
|
||||
result.value = null // Reset the result when showing the dialog
|
||||
uiState.update {
|
||||
it.copy(
|
||||
isShown = true,
|
||||
accessCode = "",
|
||||
useBiometricVisible = hasBiometry,
|
||||
onAccessCodeChange = ::onAccessCodeChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun waitResult(): HotWalletPasswordRequester.Result {
|
||||
return result.filterNotNull().first().also { result.value = null }
|
||||
}
|
||||
|
||||
suspend fun wrongAccessCode() {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
wrongAccessCode = true,
|
||||
onAccessCodeChange = {},
|
||||
)
|
||||
}
|
||||
delay(timeMillis = 500) // Delay to show the wrong access code state
|
||||
}
|
||||
|
||||
private fun getInitialState() = HotAccessCodeRequestUM(
|
||||
onDismiss = ::dismiss,
|
||||
onAccessCodeChange = ::onAccessCodeChange,
|
||||
accessCode = "",
|
||||
useBiometricClick = {
|
||||
dismissState()
|
||||
result.value = HotWalletPasswordRequester.Result.UseBiometry
|
||||
},
|
||||
)
|
||||
|
||||
private fun onAccessCodeChange(accessCode: String) {
|
||||
if (accessCode.length > ACCESS_CODE_LENGTH) return
|
||||
|
||||
uiState.update {
|
||||
it.copy(accessCode = accessCode, wrongAccessCode = false)
|
||||
}
|
||||
|
||||
if (accessCode.length == ACCESS_CODE_LENGTH) {
|
||||
uiState.update {
|
||||
it.copy(onAccessCodeChange = {})
|
||||
}
|
||||
|
||||
result.value = HotWalletPasswordRequester.Result.EnteredPassword(HotAuth.Password(accessCode.toCharArray()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissState() {
|
||||
uiState.update {
|
||||
it.copy(isShown = false)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ACCESS_CODE_LENGTH = 6
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.features.hotwallet.accesscoderequest.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
|
||||
import com.tangem.features.hotwallet.HotWalletPasswordRequester
|
||||
import com.tangem.features.hotwallet.accesscoderequest.DefaultHotAccessCodeRequestComponent
|
||||
import com.tangem.features.hotwallet.accesscoderequest.HotAccessCodeRequestModel
|
||||
import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ComponentModuleBinds {
|
||||
|
||||
@Binds
|
||||
fun bindComponentFactory(impl: DefaultHotAccessCodeRequestComponent.Factory): HotAccessCodeRequestComponent.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(HotAccessCodeRequestModel::class)
|
||||
fun bindCreateMobileWalletModel(model: HotAccessCodeRequestModel): Model
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindHotWalletPasswordRequester(impl: HotWalletPasswordRequesterProxy): HotWalletPasswordRequester
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.hotwallet.accesscoderequest.entity
|
||||
|
||||
internal data class HotAccessCodeRequestUM(
|
||||
val isShown: Boolean = false,
|
||||
val accessCode: String = "",
|
||||
val wrongAccessCode: Boolean = false,
|
||||
val useBiometricVisible: Boolean = true,
|
||||
val useBiometricClick: () -> Unit = {},
|
||||
val onAccessCodeChange: (String) -> Unit = {},
|
||||
val onDismiss: () -> Unit = {},
|
||||
)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.features.hotwallet.accesscoderequest.proxy
|
||||
|
||||
import com.tangem.features.hotwallet.HotWalletPasswordRequester
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class HotWalletPasswordRequesterProxy @Inject constructor() : HotWalletPasswordRequester {
|
||||
|
||||
val componentRequester = MutableStateFlow<HotWalletPasswordRequester?>(null)
|
||||
|
||||
override suspend fun wrongPassword() {
|
||||
call { wrongPassword() }
|
||||
}
|
||||
|
||||
override suspend fun requestPassword(hasBiometry: Boolean): HotWalletPasswordRequester.Result =
|
||||
call { requestPassword(hasBiometry) }
|
||||
|
||||
override suspend fun dismiss() {
|
||||
call { dismiss() }
|
||||
}
|
||||
|
||||
private suspend fun <T> call(block: suspend HotWalletPasswordRequester.() -> T): T {
|
||||
return withTimeout(timeMillis = 1000) {
|
||||
componentRequester.filterNotNull().first()
|
||||
}.block()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.tangem.features.hotwallet.accesscoderequest.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH24
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.fields.PinTextField
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.res.LocalHapticManager
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.hotwallet.accesscoderequest.entity.HotAccessCodeRequestUM
|
||||
import com.tangem.features.hotwallet.impl.R
|
||||
|
||||
@Suppress("MagicNumber", "LongMethod")
|
||||
@Composable
|
||||
internal fun HotAccessCodeRequestFullScreenContent(state: HotAccessCodeRequestUM, modifier: Modifier = Modifier) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
visible = state.isShown,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM.Back(state.onDismiss),
|
||||
)
|
||||
|
||||
SpacerH(68.dp)
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
text = stringResourceSafe(R.string.access_code_check_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
||||
SpacerH24()
|
||||
|
||||
PinTextField(
|
||||
modifier = Modifier.animateEnterExit(
|
||||
enter = slideInVertically(
|
||||
tween(),
|
||||
initialOffsetY = { it + 200 },
|
||||
) + fadeIn(tween()),
|
||||
exit = slideOutVertically(tween(300)) { it - 200 } + fadeOut(tween()),
|
||||
),
|
||||
length = 6,
|
||||
isPasswordVisual = true,
|
||||
value = state.accessCode,
|
||||
wrongCode = state.wrongAccessCode,
|
||||
onValueChange = state.onAccessCodeChange,
|
||||
)
|
||||
}
|
||||
|
||||
if (state.useBiometricVisible) {
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding()
|
||||
.imePadding(),
|
||||
text = "Use biometric",
|
||||
onClick = state.useBiometricClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val hapticManager = LocalHapticManager.current
|
||||
|
||||
LaunchedEffect(state.wrongAccessCode) {
|
||||
if (state.wrongAccessCode) {
|
||||
hapticManager.perform(TangemHapticEffect.View.Reject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.secondary),
|
||||
) {
|
||||
var isShown by remember { mutableStateOf(true) }
|
||||
|
||||
HotAccessCodeRequestFullScreenContent(
|
||||
state = HotAccessCodeRequestUM(isShown = isShown),
|
||||
modifier = Modifier,
|
||||
)
|
||||
|
||||
Button(onClick = { isShown = !isShown }) {
|
||||
Text("TOggle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.model.AddExistingWalletImportModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.ui.AddExistingWalletImportContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
|
|||
|
|
@ -1,5 +1,23 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port.entity
|
||||
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class AddExistingWalletImportUM(
|
||||
val onBackClick: () -> Unit,
|
||||
val words: TextFieldValue,
|
||||
val wordsChange: (TextFieldValue) -> Unit,
|
||||
val passPhrase: TextFieldValue,
|
||||
val passPhraseChange: (TextFieldValue) -> Unit,
|
||||
val onPassphraseInfoClick: () -> Unit,
|
||||
val wordsErrorText: TextReference?,
|
||||
val invalidWords: ImmutableList<String>,
|
||||
val createWalletEnabled: Boolean,
|
||||
val createWalletProgress: Boolean,
|
||||
val createWalletClick: () -> Unit,
|
||||
val suggestionsList: ImmutableList<String>,
|
||||
val onSuggestionClick: (String) -> Unit,
|
||||
val infoBottomSheetConfig: TangemBottomSheetConfig,
|
||||
val readyToImport: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port.model
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.features.hotwallet.MnemonicRepository
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.entity.AddExistingWalletImportUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class AddExistingWalletImportModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val mnemonicRepository: MnemonicRepository,
|
||||
) : Model() {
|
||||
|
||||
private val importSeedPhraseUiStateBuilder: ImportSeedPhraseUiStateBuilder
|
||||
|
||||
init {
|
||||
importSeedPhraseUiStateBuilder = ImportSeedPhraseUiStateBuilder(
|
||||
modelScope = modelScope,
|
||||
mnemonicRepository = mnemonicRepository,
|
||||
updateUiState = { block -> uiState.update { block(it) } },
|
||||
readyToImport = { ready -> uiState.update { it.copy(readyToImport = ready) } },
|
||||
importWallet = { mnemonic: Mnemonic, passphrase: String? ->
|
||||
importWallet(
|
||||
mnemonic = mnemonic,
|
||||
passphrase = passphrase,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
internal val uiState: StateFlow<AddExistingWalletImportUM>
|
||||
field = MutableStateFlow(importSeedPhraseUiStateBuilder.getState())
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun importWallet(mnemonic: Mnemonic, passphrase: String?) {
|
||||
// TODO implement importing seed phrase
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port.model
|
||||
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.crypto.bip39.MnemonicErrorResult
|
||||
import com.tangem.features.hotwallet.MnemonicRepository
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.entity.AddExistingWalletImportUM
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class ImportSeedPhraseUiStateBuilder(
|
||||
private val modelScope: CoroutineScope,
|
||||
private val mnemonicRepository: MnemonicRepository,
|
||||
private val readyToImport: (Boolean) -> Unit,
|
||||
private val updateUiState: ((AddExistingWalletImportUM) -> AddExistingWalletImportUM) -> Unit,
|
||||
private val importWallet: (mnemonic: Mnemonic, passphrase: String?) -> Unit,
|
||||
) {
|
||||
private val wordsCheckJobHolder = JobHolder()
|
||||
private var importedMnemonic: Mnemonic? = null
|
||||
private var passphrase: String? = null
|
||||
|
||||
fun getState(): AddExistingWalletImportUM {
|
||||
return AddExistingWalletImportUM(
|
||||
words = TextFieldValue(""),
|
||||
passPhrase = TextFieldValue(""),
|
||||
wordsErrorText = null,
|
||||
invalidWords = persistentListOf(),
|
||||
createWalletEnabled = false,
|
||||
createWalletProgress = false,
|
||||
suggestionsList = persistentListOf(),
|
||||
wordsChange = {
|
||||
launchInterceptWords(wordsField = it)
|
||||
suggestNextWord(it)
|
||||
updateUiState { state ->
|
||||
state.copy(words = it)
|
||||
}
|
||||
},
|
||||
passPhraseChange = {
|
||||
passphrase = it.text
|
||||
updateUiState { state -> state.copy(passPhrase = it) }
|
||||
},
|
||||
onPassphraseInfoClick = ::showInfoBS,
|
||||
createWalletClick = ::onCreateWallet,
|
||||
onSuggestionClick = { word -> addSuggestedWord(word) },
|
||||
readyToImport = false,
|
||||
infoBottomSheetConfig = TangemBottomSheetConfig.Empty,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onCreateWallet() {
|
||||
val mnemonic = importedMnemonic ?: return
|
||||
val passphrase = passphrase?.takeIf { it.isNotEmpty() }
|
||||
importWallet(mnemonic, passphrase)
|
||||
}
|
||||
|
||||
private fun addSuggestedWord(word: String) {
|
||||
updateUiState { st ->
|
||||
val text = st.words.text
|
||||
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
|
||||
val newWords = wordsFromText.dropLast(1) + word
|
||||
val newWordsText = newWords.joinToString(" ")
|
||||
st.copy(
|
||||
words = TextFieldValue(
|
||||
text = newWordsText,
|
||||
selection = TextRange(newWordsText.length),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun suggestNextWord(wordsField: TextFieldValue) {
|
||||
if (wordsField.text.endsWith(" ")) {
|
||||
updateUiState { it.copy(suggestionsList = emptyList<String>().toImmutableList()) }
|
||||
return
|
||||
}
|
||||
|
||||
val text = wordsField.text
|
||||
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
|
||||
val lastWord = wordsFromText.lastOrNull() ?: return
|
||||
if (lastWord.length < MINIMUM_WORD_LENGTH) return
|
||||
|
||||
if (mnemonicRepository.words.contains(lastWord) && mnemonicRepository.words.none { it.startsWith(lastWord) }) {
|
||||
updateUiState { it.copy(suggestionsList = emptyList<String>().toImmutableList()) }
|
||||
return
|
||||
}
|
||||
|
||||
updateUiState { st ->
|
||||
st.copy(
|
||||
suggestionsList = mnemonicRepository.words
|
||||
.filter { it.startsWith(lastWord) && it != lastWord }
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchInterceptWords(wordsField: TextFieldValue) {
|
||||
modelScope.launch {
|
||||
delay(timeMillis = WORDS_INTERCEPT_DELAY_MS)
|
||||
interceptWords(wordsField)
|
||||
}.saveIn(wordsCheckJobHolder)
|
||||
}
|
||||
|
||||
private fun interceptWords(wordsField: TextFieldValue) {
|
||||
readyToImport(false)
|
||||
|
||||
updateUiState {
|
||||
it.copy(
|
||||
createWalletEnabled = false,
|
||||
wordsErrorText = null,
|
||||
)
|
||||
}
|
||||
|
||||
importedMnemonic = null
|
||||
|
||||
val text = wordsField.text
|
||||
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
|
||||
val invalidWords = wordsFromText.filterNot { it in mnemonicRepository.words }
|
||||
if (invalidWords.isNotEmpty()) {
|
||||
updateUiState {
|
||||
it.copy(
|
||||
invalidWords = invalidWords.toImmutableList(),
|
||||
wordsErrorText = resourceReference(R.string.onboarding_seed_mnemonic_wrong_words),
|
||||
createWalletEnabled = false,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val mnemonic = mnemonicRepository.generateMnemonic(text)
|
||||
importedMnemonic = mnemonic
|
||||
updateUiState {
|
||||
it.copy(
|
||||
invalidWords = emptyList<String>().toImmutableList(),
|
||||
wordsErrorText = null,
|
||||
createWalletEnabled = true,
|
||||
)
|
||||
}
|
||||
readyToImport(true)
|
||||
} catch (ex: TangemSdkError.MnemonicException) {
|
||||
readyToImport(false)
|
||||
val error = ex.mnemonicResult
|
||||
if (error is MnemonicErrorResult.InvalidChecksum) {
|
||||
updateUiState {
|
||||
it.copy(
|
||||
wordsErrorText = resourceReference(R.string.onboarding_seed_mnemonic_invalid_checksum),
|
||||
createWalletEnabled = false,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
updateUiState {
|
||||
it.copy(
|
||||
createWalletEnabled = false,
|
||||
wordsErrorText = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showInfoBS() {
|
||||
updateUiState { state ->
|
||||
state.copy(
|
||||
infoBottomSheetConfig = TangemBottomSheetConfig.Companion.Empty.copy(
|
||||
isShown = true,
|
||||
onDismissRequest = {
|
||||
updateUiState { it.copy(infoBottomSheetConfig = TangemBottomSheetConfig.Companion.Empty) }
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MINIMUM_WORD_LENGTH = 2
|
||||
private const val WORDS_INTERCEPT_DELAY_MS = 500L
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,218 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.addexistingwallet.im.port.entity.AddExistingWalletImportUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.Keyboard
|
||||
import com.tangem.core.ui.components.Notifier
|
||||
import com.tangem.core.ui.components.OutlineTextFieldWithIcon
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.TangemTextFieldsDefault
|
||||
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.features.hotwallet.addexistingwallet.im.port.ui.utils.InvalidWordsColorTransformation
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun AddExistingWalletImportContent(state: AddExistingWalletImportUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
Box(modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
startButton = TopAppBarButtonUM.Back(state.onBackClick),
|
||||
title = stringResourceSafe(R.string.wallet_import_title),
|
||||
.fillMaxSize()
|
||||
.imePadding(),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.onboarding_seed_import_message),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(start = 36.dp, end = 36.dp, top = 24.dp, bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
PhraseBlock(
|
||||
state = state,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
OutlineTextFieldWithIcon(
|
||||
modifier = modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
value = state.passPhrase,
|
||||
onValueChange = state.passPhraseChange,
|
||||
iconResId = R.drawable.ic_information_24,
|
||||
iconColor = TangemTheme.colors.icon.informative,
|
||||
label = stringResourceSafe(id = R.string.common_passphrase),
|
||||
placeholder = stringResourceSafe(id = R.string.send_optional_field),
|
||||
onIconClick = state.onPassphraseInfoClick,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
PrimaryButtonIconEnd(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.common_import),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
enabled = state.createWalletEnabled,
|
||||
showProgress = state.createWalletProgress,
|
||||
onClick = state.createWalletClick,
|
||||
)
|
||||
}
|
||||
|
||||
val keyboard by keyboardAsState()
|
||||
if (keyboard is Keyboard.Opened) {
|
||||
SuggestionsBlock(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.imePadding(),
|
||||
suggestionsList = state.suggestionsList,
|
||||
onClick = { index -> state.onSuggestionClick(state.suggestionsList[index]) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PassphraseInfoBottomSheet(state.infoBottomSheetConfig)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PhraseBlock(state: AddExistingWalletImportUM, modifier: Modifier = Modifier) {
|
||||
val warningColor = TangemTheme.colors.text.warning
|
||||
val invalidWordsColorTransformation = remember(state.invalidWords, warningColor) {
|
||||
InvalidWordsColorTransformation(
|
||||
wordsToBrush = state.invalidWords,
|
||||
style = SpanStyle(color = warningColor),
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size142),
|
||||
value = state.words,
|
||||
onValueChange = state.wordsChange,
|
||||
textStyle = TangemTheme.typography.body1,
|
||||
singleLine = false,
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
visualTransformation = invalidWordsColorTransformation,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
),
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.height(TangemTheme.dimens.size32),
|
||||
) {
|
||||
if (state.wordsErrorText != null) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
text = state.wordsErrorText.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.warning,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SuggestionsBlock(
|
||||
suggestionsList: ImmutableList<String>,
|
||||
onClick: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
enter = fadeIn() + slideIn(initialOffset = { IntOffset(x = 200, y = 0) }),
|
||||
exit = slideOut(targetOffset = { IntOffset(x = -200, y = 0) }) + fadeOut(),
|
||||
visible = suggestionsList.isNotEmpty(),
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16),
|
||||
) {
|
||||
items(suggestionsList.size) { index ->
|
||||
SuggestionButton(
|
||||
modifier = Modifier.rowPadding(
|
||||
index = index,
|
||||
rowSize = suggestionsList.size,
|
||||
outSide = TangemTheme.dimens.size0,
|
||||
inSide = TangemTheme.dimens.size4,
|
||||
),
|
||||
text = suggestionsList[index],
|
||||
onClick = { onClick(index) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SuggestionButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Notifier(
|
||||
text = text,
|
||||
modifier = modifier
|
||||
.clip(TangemTheme.shapes.roundedCorners8)
|
||||
.clickable(onClick = onClick),
|
||||
backgroundColor = TangemTheme.colors.icon.primary1,
|
||||
textColor = TangemTheme.colors.text.primary2,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: Dp): Modifier = when (index) {
|
||||
0 -> this.padding(start = outSide, end = inSide)
|
||||
rowSize - 1 -> this.padding(start = inSide, end = outSide)
|
||||
else -> this.padding(horizontal = inSide)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
|
|
@ -40,7 +222,20 @@ private fun PreviewAddExistingWalletImportContent() {
|
|||
TangemThemePreview {
|
||||
AddExistingWalletImportContent(
|
||||
state = AddExistingWalletImportUM(
|
||||
onBackClick = {},
|
||||
words = TextFieldValue(""),
|
||||
wordsChange = {},
|
||||
passPhrase = TextFieldValue(""),
|
||||
passPhraseChange = {},
|
||||
onPassphraseInfoClick = {},
|
||||
wordsErrorText = null,
|
||||
invalidWords = persistentListOf(),
|
||||
createWalletEnabled = false,
|
||||
createWalletProgress = false,
|
||||
createWalletClick = {},
|
||||
suggestionsList = persistentListOf(),
|
||||
onSuggestionClick = {},
|
||||
infoBottomSheetConfig = TangemBottomSheetConfig.Empty,
|
||||
readyToImport = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port.ui
|
||||
|
||||
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.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.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.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
fun PassphraseInfoBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
) { _: TangemBottomSheetConfigContent.Empty ->
|
||||
PassphraseInfoBottomSheetContent(config.onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PassphraseInfoBottomSheetContent(onDismiss: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = TangemTheme.dimens.size40)
|
||||
.size(TangemTheme.dimens.size48),
|
||||
painter = painterResource(id = R.drawable.ic_information_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.common_passphrase),
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.size40)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.h2,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.onboarding_bottom_sheet_passphrase_description),
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.size16)
|
||||
.padding(horizontal = TangemTheme.dimens.size24)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
style = TangemTheme.typography.body2,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.size16)
|
||||
.padding(top = TangemTheme.dimens.size40)
|
||||
.padding(bottom = TangemTheme.dimens.size32)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.common_ok),
|
||||
onClick = onDismiss,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PassphraseInfoBottomSheetContentPreview() {
|
||||
TangemThemePreview {
|
||||
PassphraseInfoBottomSheetContent({ })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port.ui.utils
|
||||
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.input.OffsetMapping
|
||||
import androidx.compose.ui.text.input.TransformedText
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import com.tangem.utils.StringsSigns.WHITE_SPACE
|
||||
import com.tangem.utils.extensions.isSingleItem
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
class InvalidWordsColorTransformation(
|
||||
private val wordsToBrush: ImmutableList<String>,
|
||||
private val style: SpanStyle,
|
||||
) : VisualTransformation {
|
||||
|
||||
override fun filter(text: AnnotatedString): TransformedText {
|
||||
val plainText = text.text
|
||||
if (wordsToBrush.isSingleItem() && wordsToBrush.first() == plainText) {
|
||||
return plainText.annotate().toTransformedText()
|
||||
}
|
||||
|
||||
val wordList = makeSeparatedWordlist(plainText)
|
||||
val annotatedString = makeAnnotatedString(wordList)
|
||||
|
||||
return annotatedString.toTransformedText()
|
||||
}
|
||||
|
||||
private fun makeSeparatedWordlist(plainText: String): List<String> {
|
||||
val plainTextCharArray = plainText.toCharArray()
|
||||
val wordlist = mutableListOf<String>()
|
||||
var wordBuilder = StringBuilder()
|
||||
|
||||
for (index in plainTextCharArray.indices) {
|
||||
val char = plainTextCharArray[index]
|
||||
if (char.isWhitespace()) {
|
||||
if (wordBuilder.isEmpty()) {
|
||||
wordlist.add(WORD_SEPARATOR)
|
||||
} else {
|
||||
wordlist.add(wordBuilder.toString())
|
||||
wordBuilder = StringBuilder()
|
||||
wordlist.add(WORD_SEPARATOR)
|
||||
}
|
||||
} else {
|
||||
if (index == plainTextCharArray.lastIndex) {
|
||||
wordBuilder.append(char)
|
||||
wordlist.add(wordBuilder.toString())
|
||||
wordBuilder = StringBuilder()
|
||||
} else {
|
||||
wordBuilder.append(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
return wordlist.toList()
|
||||
}
|
||||
|
||||
private fun makeAnnotatedString(wordList: List<String>): AnnotatedString = buildAnnotatedString {
|
||||
wordList.forEach {
|
||||
if (it == WORD_SEPARATOR) {
|
||||
append(WHITE_SPACE)
|
||||
} else if (wordsToBrush.contains(it)) {
|
||||
append(it.annotate())
|
||||
} else {
|
||||
append(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.annotate(): AnnotatedString = AnnotatedString(this, style)
|
||||
|
||||
private fun AnnotatedString.toTransformedText(): TransformedText = TransformedText(this, OffsetMapping.Identity)
|
||||
|
||||
companion object {
|
||||
private const val WORD_SEPARATOR = "_$@-*"
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,11 @@ import com.arkivanov.decompose.router.stack.push
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletRoute
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -20,6 +22,8 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
|
||||
val addExistingWalletStartModelCallbacks = AddExistingWalletStartModelCallbacks()
|
||||
val addExistingWalletImportModelCallbacks = AddExistingWalletImportModelCallbacks()
|
||||
val pushNotificationsComponentModelCallbacks = PushNotificationsComponentModelCallbacks()
|
||||
val accessCodeModelCallbacks = AccessCodeModelCallbacks()
|
||||
|
||||
val stackNavigation = StackNavigation<AddExistingWalletRoute>()
|
||||
|
||||
|
|
@ -38,4 +42,20 @@ internal class AddExistingWalletModel @Inject constructor(
|
|||
stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
|
||||
inner class PushNotificationsComponentModelCallbacks : PushNotificationsComponent.ModelCallbacks {
|
||||
override fun onResult() {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
}
|
||||
|
||||
inner class AccessCodeModelCallbacks : SetAccessCodeComponent.ModelCallbacks {
|
||||
override fun onBackClick() {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
|
||||
override fun onAccessCodeSet() {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,11 @@ package com.tangem.features.hotwallet.addexistingwallet.root.di
|
|||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.hotwallet.AddExistingWalletComponent
|
||||
import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.AddExistingWalletModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.DefaultAddExistingWalletComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.model.AddExistingWalletImportModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -42,4 +43,9 @@ internal interface AddExistingWalletModuleBinds {
|
|||
@IntoMap
|
||||
@ClassKey(AddExistingWalletImportModel::class)
|
||||
fun bindAddExistingWalletImportModel(model: AddExistingWalletImportModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SetAccessCodeModel::class)
|
||||
fun bindAccessCodeModel(model: SetAccessCodeModel): Model
|
||||
}
|
||||
|
|
@ -2,12 +2,16 @@ package com.tangem.features.hotwallet.addexistingwallet.root.routing
|
|||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.root.AddExistingWalletModel
|
||||
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AddExistingWalletChildFactory @Inject constructor() {
|
||||
internal class AddExistingWalletChildFactory @Inject constructor(
|
||||
private val pushNotificationsComponent: PushNotificationsComponent.Factory,
|
||||
) {
|
||||
|
||||
fun createChild(
|
||||
route: AddExistingWalletRoute,
|
||||
|
|
@ -27,6 +31,18 @@ internal class AddExistingWalletChildFactory @Inject constructor() {
|
|||
callbacks = model.addExistingWalletImportModelCallbacks,
|
||||
),
|
||||
)
|
||||
is AddExistingWalletRoute.PushNotifications -> pushNotificationsComponent.create(
|
||||
context = childContext,
|
||||
params = PushNotificationsComponent.Params.Callbacks(
|
||||
callbacks = model.pushNotificationsComponentModelCallbacks,
|
||||
),
|
||||
)
|
||||
is AddExistingWalletRoute.AccessCode -> SetAccessCodeComponent(
|
||||
context = childContext,
|
||||
params = SetAccessCodeComponent.Params(
|
||||
callbacks = model.accessCodeModelCallbacks,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,4 +10,10 @@ internal sealed class AddExistingWalletRoute : Route {
|
|||
|
||||
@Serializable
|
||||
object Import : AddExistingWalletRoute()
|
||||
|
||||
@Serializable
|
||||
object PushNotifications : AddExistingWalletRoute()
|
||||
|
||||
@Serializable
|
||||
object AccessCode : AddExistingWalletRoute()
|
||||
}
|
||||
|
|
@ -3,27 +3,27 @@ package com.tangem.features.hotwallet.addexistingwallet.start.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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.R
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
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.addexistingwallet.start.entity.AddExistingWalletStartUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.features.hotwallet.common.ui.OptionBlock
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -62,6 +62,7 @@ internal fun AddExistingWalletStartContent(state: AddExistingWalletStartUM, modi
|
|||
OptionBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp),
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
title = stringResourceSafe(R.string.wallet_import_seed_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_seed_description),
|
||||
badge = null,
|
||||
|
|
@ -69,6 +70,7 @@ internal fun AddExistingWalletStartContent(state: AddExistingWalletStartUM, modi
|
|||
enabled = true,
|
||||
)
|
||||
OptionBlock(
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
title = stringResourceSafe(R.string.wallet_import_scan_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_scan_description),
|
||||
badge = {
|
||||
|
|
@ -85,6 +87,7 @@ internal fun AddExistingWalletStartContent(state: AddExistingWalletStartUM, modi
|
|||
enabled = true,
|
||||
)
|
||||
OptionBlock(
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
title = stringResourceSafe(R.string.wallet_import_google_drive_title),
|
||||
description = stringResourceSafe(R.string.wallet_import_google_drive_description),
|
||||
badge = {
|
||||
|
|
@ -114,58 +117,6 @@ internal fun AddExistingWalletStartContent(state: AddExistingWalletStartUM, modi
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OptionBlock(
|
||||
title: String,
|
||||
description: String,
|
||||
badge: (@Composable () -> Unit)?,
|
||||
onClick: (() -> Unit)?,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(
|
||||
color = TangemTheme.colors.background.secondary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.conditional(onClick != null) {
|
||||
onClick?.let { clickableSingle(onClick = it) } ?: Modifier
|
||||
}
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.padding(end = 4.dp),
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = if (enabled) {
|
||||
TangemTheme.colors.text.primary1
|
||||
} else {
|
||||
TangemTheme.colors.text.secondary
|
||||
},
|
||||
)
|
||||
badge?.invoke()
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp),
|
||||
text = description,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = if (enabled) {
|
||||
TangemTheme.colors.text.tertiary
|
||||
} else {
|
||||
TangemTheme.colors.text.disabled
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BuyTangemWalletBlock(onScanClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model
|
||||
package com.tangem.features.hotwallet.common.repository
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.bip39.EntropyLength
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
import com.tangem.features.hotwallet.MnemonicRepository
|
||||
import com.tangem.features.hotwallet.MnemonicRepository.MnemonicType
|
||||
import com.tangem.sdk.extensions.getWordlist
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class MnemonicRepository @Inject constructor(
|
||||
internal class DefaultMnemonicRepository @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
) : MnemonicRepository {
|
||||
private val wordlist = Wordlist.getWordlist(context)
|
||||
|
||||
val words: Set<String> = wordlist.words.toHashSet()
|
||||
override val words: Set<String> = wordlist.words.toHashSet()
|
||||
|
||||
fun generateMnemonic(type: MnemonicType = MnemonicType.Words12): Mnemonic = DefaultMnemonic(
|
||||
override fun generateMnemonic(type: MnemonicType): Mnemonic = DefaultMnemonic(
|
||||
entropy = when (type) {
|
||||
MnemonicType.Words12 -> EntropyLength.Bits128Length
|
||||
MnemonicType.Words24 -> EntropyLength.Bits256Length
|
||||
|
|
@ -24,12 +26,8 @@ internal class MnemonicRepository @Inject constructor(
|
|||
wordlist = wordlist,
|
||||
)
|
||||
|
||||
fun generateMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(
|
||||
override fun generateMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(
|
||||
mnemonic = mnemonicString,
|
||||
wordlist = wordlist,
|
||||
)
|
||||
|
||||
enum class MnemonicType {
|
||||
Words12, Words24,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.features.hotwallet.common.ui
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.conditional
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
internal const val DISABLED_COLORS_ALPHA = 0.5f
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun OptionBlock(
|
||||
title: String,
|
||||
description: String,
|
||||
badge: (@Composable () -> Unit)?,
|
||||
onClick: (() -> Unit)?,
|
||||
enabled: Boolean,
|
||||
backgroundColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (enabled) {
|
||||
backgroundColor
|
||||
} else {
|
||||
backgroundColor.copy(alpha = DISABLED_COLORS_ALPHA)
|
||||
},
|
||||
)
|
||||
val titleColor by animateColorAsState(
|
||||
targetValue = if (enabled) {
|
||||
TangemTheme.colors.text.primary1
|
||||
} else {
|
||||
TangemTheme.colors.text.primary1.copy(alpha = DISABLED_COLORS_ALPHA)
|
||||
},
|
||||
)
|
||||
val descriptionColor by animateColorAsState(
|
||||
targetValue = if (enabled) {
|
||||
TangemTheme.colors.text.tertiary
|
||||
} else {
|
||||
TangemTheme.colors.text.tertiary.copy(alpha = DISABLED_COLORS_ALPHA)
|
||||
},
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp)
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(
|
||||
color = backgroundColor,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.conditional(onClick != null) {
|
||||
onClick?.let { clickableSingle(onClick = it) } ?: Modifier
|
||||
}
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = false)
|
||||
.padding(end = 4.dp),
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = titleColor,
|
||||
)
|
||||
badge?.invoke()
|
||||
}
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp),
|
||||
text = description,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = descriptionColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,30 @@
|
|||
package com.tangem.features.hotwallet.createmobilewallet
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.wallets.builder.HotUserWalletBuilder
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.features.hotwallet.createmobilewallet.entity.CreateMobileWalletUM
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.hot.sdk.model.HotAuth
|
||||
import com.tangem.hot.sdk.model.MnemonicType
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class CreateMobileWalletModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val hotUserWalletBuilderFactory: HotUserWalletBuilder.Factory,
|
||||
private val saveUserWalletUseCase: SaveWalletUseCase,
|
||||
private val router: Router,
|
||||
private val tangemHotSdk: TangemHotSdk,
|
||||
) : Model() {
|
||||
|
||||
internal val uiState: StateFlow<CreateMobileWalletUM>
|
||||
|
|
@ -20,10 +32,30 @@ internal class CreateMobileWalletModel @Inject constructor(
|
|||
CreateMobileWalletUM(
|
||||
onBackClick = { router.pop() },
|
||||
onCreateClick = ::onCreateClick,
|
||||
createButtonLoading = false,
|
||||
),
|
||||
)
|
||||
|
||||
private fun onCreateClick() {
|
||||
// TODO create a wallet
|
||||
modelScope.launch {
|
||||
uiState.update {
|
||||
it.copy(createButtonLoading = true)
|
||||
}
|
||||
|
||||
runCatching {
|
||||
val hotWalletId = tangemHotSdk.generateWallet(HotAuth.NoAuth, mnemonicType = MnemonicType.Words12)
|
||||
val hotUserWalletBuilder = hotUserWalletBuilderFactory.create(hotWalletId)
|
||||
saveUserWalletUseCase(
|
||||
hotUserWalletBuilder.build(),
|
||||
)
|
||||
router.push(AppRoute.Wallet)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
|
||||
uiState.update {
|
||||
it.copy(createButtonLoading = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,10 +12,12 @@ import dagger.assisted.Assisted
|
|||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultCreateMobileWalletComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Unit,
|
||||
) : CreateMobileWalletComponent, AppComponentContext by context {
|
||||
|
||||
private val model: CreateMobileWalletModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -10,18 +10,12 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object CreateMobileWalletModule
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface CreateMobileWalletModuleBinds {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindCreateMobileWalletComponentFactory(
|
||||
impl: DefaultCreateMobileWalletComponent.Factory,
|
||||
): CreateMobileWalletComponent.Factory
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.hotwallet.createmobilewallet.entity
|
||||
|
||||
internal data class CreateMobileWalletUM(
|
||||
val createButtonLoading: Boolean,
|
||||
val onBackClick: () -> Unit,
|
||||
val onCreateClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -84,8 +84,8 @@ internal fun CreateMobileWalletContent(state: CreateMobileWalletUM, modifier: Mo
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
text = "Create",
|
||||
showProgress = false,
|
||||
text = stringResourceSafe(R.string.common_create),
|
||||
showProgress = state.createButtonLoading,
|
||||
enabled = true,
|
||||
onClick = state.onCreateClick,
|
||||
)
|
||||
|
|
@ -133,6 +133,7 @@ private fun PreviewCreateWalletContent() {
|
|||
CreateMobileWalletContent(
|
||||
state = CreateMobileWalletUM(
|
||||
onBackClick = {},
|
||||
createButtonLoading = false,
|
||||
onCreateClick = {},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package com.tangem.features.hotwallet.di
|
|||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.hotwallet.DefaultHotWalletFeatureToggles
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.hotwallet.MnemonicRepository
|
||||
import com.tangem.features.hotwallet.common.repository.DefaultMnemonicRepository
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -22,4 +25,8 @@ internal object HotWalletFeatureModule {
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface HotWalletFeatureModuleBinds
|
||||
internal interface HotWalletFeatureModuleBinds {
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideMnemonicRepository(repository: DefaultMnemonicRepository): MnemonicRepository
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.check
|
||||
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.features.hotwallet.manualbackup.check.model.ManualBackupCheckModel
|
||||
import com.tangem.features.hotwallet.manualbackup.check.ui.ManualBackupCheckContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class ManualBackupCheckComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
private val model: ManualBackupCheckModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
ManualBackupCheckContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onCompleteClick()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val generatedWords: Mnemonic,
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.check.entity
|
||||
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class ManualBackupCheckUM(
|
||||
val onCompleteButtonClick: () -> Unit,
|
||||
val wordFields: ImmutableList<WordField>,
|
||||
val completeButtonEnabled: Boolean,
|
||||
val completeButtonProgress: Boolean,
|
||||
) {
|
||||
data class WordField(
|
||||
val index: Int,
|
||||
val word: TextFieldValue,
|
||||
val error: Boolean,
|
||||
val onChange: (TextFieldValue) -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.check.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.hotwallet.manualbackup.check.ManualBackupCheckComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.check.entity.ManualBackupCheckUM
|
||||
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 javax.inject.Inject
|
||||
import kotlin.Boolean
|
||||
import kotlin.Int
|
||||
import kotlin.String
|
||||
import kotlin.Suppress
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.all
|
||||
import kotlin.collections.map
|
||||
import kotlin.error
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class ManualBackupCheckModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<ManualBackupCheckComponent.Params>()
|
||||
private val callbacks = params.callbacks
|
||||
|
||||
internal val uiState: StateFlow<ManualBackupCheckUM>
|
||||
field = MutableStateFlow(getInitialUIState())
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getInitialUIState(): ManualBackupCheckUM {
|
||||
val wordFields = List(WORD_FIELD_INDICES.size) { index ->
|
||||
val shownIndex = WORD_FIELD_INDICES[index]
|
||||
|
||||
ManualBackupCheckUM.WordField(
|
||||
index = shownIndex,
|
||||
word = TextFieldValue(""),
|
||||
error = false,
|
||||
onChange = { textFieldValue ->
|
||||
updateWordField(shownIndex, textFieldValue)
|
||||
},
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
return ManualBackupCheckUM(
|
||||
wordFields = wordFields,
|
||||
completeButtonEnabled = false,
|
||||
completeButtonProgress = false,
|
||||
onCompleteButtonClick = {
|
||||
val currentUIState = uiState.value
|
||||
if (currentUIState.completeButtonEnabled) {
|
||||
callbacks.onCompleteClick()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateWordField(shownIndex: Int, newText: TextFieldValue) {
|
||||
uiState.update { currentState ->
|
||||
val updatedFields = currentState.wordFields.map { wordField ->
|
||||
if (wordField.index == shownIndex) {
|
||||
val isCorrect = checkWordField(newText.text, shownIndex)
|
||||
wordField.copy(
|
||||
word = newText,
|
||||
error = !isCorrect,
|
||||
)
|
||||
} else {
|
||||
wordField
|
||||
}
|
||||
}.toImmutableList()
|
||||
|
||||
val allFieldsCorrect = updatedFields.all { field ->
|
||||
checkWordField(field.word.text, field.index)
|
||||
}
|
||||
|
||||
currentState.copy(
|
||||
wordFields = updatedFields,
|
||||
completeButtonEnabled = allFieldsCorrect,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkWordField(word: String, shownIndex: Int): Boolean {
|
||||
val generatedWords = params.generatedWords
|
||||
val wordList = generatedWords.mnemonicComponents
|
||||
|
||||
return if (shownIndex <= wordList.size) {
|
||||
wordList[shownIndex - 1] == word
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val WORD_FIELD_INDICES = listOf(2, 7, 11)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.check.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.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.tangem.core.ui.components.OutlineTextField
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.fields.contextmenu.DisableContextMenu
|
||||
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.manualbackup.check.entity.ManualBackupCheckUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun ManualBackupCheckContent(state: ManualBackupCheckUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.imePadding(),
|
||||
) {
|
||||
Column(
|
||||
Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.onboarding_seed_user_validation_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(top = 48.dp, start = 36.dp, end = 36.dp, bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.onboarding_seed_user_validation_message),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 36.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Fields(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.padding(vertical = 30.dp, horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.imePadding()
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.common_continue),
|
||||
enabled = state.completeButtonEnabled,
|
||||
showProgress = state.completeButtonProgress,
|
||||
onClick = state.onCompleteButtonClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Fields(state: ManualBackupCheckUM, modifier: Modifier = Modifier) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
DisableContextMenu {
|
||||
state.wordFields.fastForEachIndexed { index, field ->
|
||||
OutlineTextField(
|
||||
value = field.word,
|
||||
onValueChange = field.onChange,
|
||||
label = field.index.toString(),
|
||||
isError = field.error,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = if (index == state.wordFields.lastIndex) ImeAction.Done else ImeAction.Next,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { focusManager.clearFocus() },
|
||||
onNext = { focusManager.moveFocus(focusDirection = FocusDirection.Down) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
ManualBackupCheckContent(
|
||||
state = ManualBackupCheckUM(
|
||||
onCompleteButtonClick = {},
|
||||
wordFields = persistentListOf(
|
||||
ManualBackupCheckUM.WordField(
|
||||
index = 2,
|
||||
word = TextFieldValue(text = "word"),
|
||||
onChange = {},
|
||||
error = false,
|
||||
),
|
||||
ManualBackupCheckUM.WordField(
|
||||
index = 7,
|
||||
word = TextFieldValue(text = "wor"),
|
||||
onChange = {},
|
||||
error = true,
|
||||
),
|
||||
ManualBackupCheckUM.WordField(
|
||||
index = 11,
|
||||
word = TextFieldValue(text = "word"),
|
||||
onChange = {},
|
||||
error = false,
|
||||
),
|
||||
),
|
||||
completeButtonEnabled = false,
|
||||
completeButtonProgress = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.completed
|
||||
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.completed.ui.ManualBackupCompletedContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class ManualBackupCompletedComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
private val model: ManualBackupCompletedModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
ManualBackupCompletedContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onContinueClick()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,26 +1,26 @@
|
|||
package com.tangem.features.hotwallet.addexistingwallet.im.port
|
||||
package com.tangem.features.hotwallet.manualbackup.completed
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.hotwallet.addexistingwallet.im.port.entity.AddExistingWalletImportUM
|
||||
import com.tangem.features.hotwallet.manualbackup.completed.entity.ManualBackupCompletedUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class AddExistingWalletImportModel @Inject constructor(
|
||||
internal class ManualBackupCompletedModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params: AddExistingWalletImportComponent.Params = paramsContainer.require()
|
||||
private val params: ManualBackupCompletedComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<AddExistingWalletImportUM>
|
||||
internal val uiState: StateFlow<ManualBackupCompletedUM>
|
||||
field = MutableStateFlow(
|
||||
AddExistingWalletImportUM(
|
||||
onBackClick = params.callbacks::onBackClick,
|
||||
ManualBackupCompletedUM(
|
||||
onContinueClick = params.callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.completed.entity
|
||||
|
||||
internal data class ManualBackupCompletedUM(
|
||||
val onContinueClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.completed.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
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.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.features.hotwallet.manualbackup.completed.entity.ManualBackupCompletedUM
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun ManualBackupCompletedContent(state: ManualBackupCompletedUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_success_blue_76),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 48.dp,
|
||||
top = 20.dp,
|
||||
end = 48.dp,
|
||||
),
|
||||
text = stringResourceSafe(R.string.backup_complete_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 48.dp,
|
||||
top = 12.dp,
|
||||
end = 48.dp,
|
||||
),
|
||||
text = stringResourceSafe(R.string.backup_complete_description),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(2f))
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.common_continue),
|
||||
showProgress = false,
|
||||
enabled = true,
|
||||
onClick = state.onContinueClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewManualBackupCompletedContent() {
|
||||
TangemThemePreview {
|
||||
ManualBackupCompletedContent(
|
||||
state = ManualBackupCompletedUM(
|
||||
onContinueClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.phrase
|
||||
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.phrase.model.ManualBackupPhraseModel
|
||||
import com.tangem.features.hotwallet.manualbackup.phrase.ui.ManualBackupPhraseContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class ManualBackupPhraseComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
private val model: ManualBackupPhraseModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
ManualBackupPhraseContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onContinueClick()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.phrase.entity
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal data class ManualBackupPhraseUM(
|
||||
val onContinueClick: () -> Unit,
|
||||
val words: ImmutableList<MnemonicGridItem> = persistentListOf(),
|
||||
) {
|
||||
data class MnemonicGridItem(
|
||||
val index: Int,
|
||||
val mnemonic: String,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.phrase.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.phrase.entity.ManualBackupPhraseUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class ManualBackupPhraseModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<ManualBackupPhraseComponent.Params>()
|
||||
private val callbacks = params.callbacks
|
||||
|
||||
internal val uiState: StateFlow<ManualBackupPhraseUM>
|
||||
field = MutableStateFlow(getInitialUIState())
|
||||
|
||||
private fun getInitialUIState(): ManualBackupPhraseUM {
|
||||
return ManualBackupPhraseUM(
|
||||
onContinueClick = callbacks::onContinueClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.phrase.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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
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.manualbackup.phrase.entity.ManualBackupPhraseUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun ManualBackupPhraseContent(state: ManualBackupPhraseUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.imePadding()
|
||||
.weight(1f),
|
||||
) {
|
||||
TitleBlock(
|
||||
state = state,
|
||||
modifier = Modifier.padding(top = 20.dp),
|
||||
)
|
||||
|
||||
SeedPhraseGridBlock(
|
||||
mnemonicGridItems = state.words,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 20.dp, bottom = 32.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.backup_seed_responsibility),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
)
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
text = stringResourceSafe(id = R.string.common_continue),
|
||||
onClick = state.onContinueClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TitleBlock(state: ManualBackupPhraseUM, 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_description,
|
||||
state.words.size,
|
||||
),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedPhraseGridBlock(
|
||||
mnemonicGridItems: ImmutableList<ManualBackupPhraseUM.MnemonicGridItem>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
VerticalGrid(
|
||||
modifier = modifier,
|
||||
items = mnemonicGridItems,
|
||||
) { item ->
|
||||
Row(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.size8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (LocalLayoutDirection.current == LayoutDirection.Ltr) {
|
||||
Text(
|
||||
modifier = Modifier.width(TangemTheme.dimens.size40),
|
||||
text = "${item.index}.",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Text(
|
||||
text = item.mnemonic,
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = item.mnemonic,
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.width(TangemTheme.dimens.size40),
|
||||
text = "${item.index}.",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private inline fun <T> VerticalGrid(
|
||||
items: ImmutableList<T>,
|
||||
modifier: Modifier = Modifier,
|
||||
crossinline content: @Composable (T) -> Unit,
|
||||
) {
|
||||
val columnLength = items.size / 2
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
repeat(2) { index ->
|
||||
Column {
|
||||
for (i in 0 until columnLength) {
|
||||
val item = items[index * columnLength + i]
|
||||
content(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
ManualBackupPhraseContent(
|
||||
state = ManualBackupPhraseUM(
|
||||
onContinueClick = {},
|
||||
words = List(12) {
|
||||
ManualBackupPhraseUM.MnemonicGridItem(
|
||||
index = it + 1,
|
||||
mnemonic = "word${it + 1}",
|
||||
)
|
||||
}.toImmutableList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.start
|
||||
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.manualbackup.start.ui.ManualBackupStartContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class ManualBackupStartComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
private val model: ManualBackupStartModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
ManualBackupStartContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onContinueClick()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.start
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.hotwallet.manualbackup.start.entity.ManualBackupStartUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class ManualBackupStartModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params: ManualBackupStartComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<ManualBackupStartUM>
|
||||
field = MutableStateFlow(
|
||||
ManualBackupStartUM(
|
||||
onContinueClick = params.callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.start.entity
|
||||
|
||||
internal data class ManualBackupStartUM(
|
||||
val seepPhraseLength: Int = 12,
|
||||
val onContinueClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
package com.tangem.features.hotwallet.manualbackup.start.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
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.manualbackup.start.entity.ManualBackupStartUM
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun ManualBackupStartContent(state: ManualBackupStartUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
top = 24.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 8.dp,
|
||||
),
|
||||
text = stringResourceSafe(R.string.backup_info_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 8.dp,
|
||||
),
|
||||
text = stringResourceSafe(
|
||||
R.string.backup_info_description,
|
||||
state.seepPhraseLength.toString(),
|
||||
),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
FeatureBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp),
|
||||
title = stringResourceSafe(R.string.backup_info_save_title),
|
||||
description = stringResourceSafe(
|
||||
R.string.backup_info_save_description,
|
||||
state.seepPhraseLength.toString(),
|
||||
),
|
||||
iconRes = R.drawable.ic_lock_24,
|
||||
)
|
||||
FeatureBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = 24.dp),
|
||||
title = stringResourceSafe(R.string.backup_info_keep_title),
|
||||
description = stringResourceSafe(R.string.backup_info_keep_description),
|
||||
iconRes = R.drawable.ic_settings_24,
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
text = stringResourceSafe(R.string.common_continue),
|
||||
showProgress = false,
|
||||
enabled = true,
|
||||
onClick = state.onContinueClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeatureBlock(title: String, description: String, iconRes: Int, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 12.dp),
|
||||
painter = painterResource(iconRes),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 4.dp),
|
||||
text = description,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewManualBackupStartContent() {
|
||||
TangemThemePreview {
|
||||
ManualBackupStartContent(
|
||||
state = ManualBackupStartUM(
|
||||
onContinueClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.features.hotwallet.setaccesscode
|
||||
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.features.hotwallet.setaccesscode.ui.SetAccessCodeContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class SetAccessCodeComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
|
||||
private val model: SetAccessCodeModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
SetAccessCodeContent(
|
||||
state = state,
|
||||
onBack = { model.onBack() },
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
DisableScreenshotsDisposableEffect()
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onBackClick()
|
||||
fun onAccessCodeSet()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.features.hotwallet.setaccesscode
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.hotwallet.setaccesscode.entity.SetAccessCodeUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class SetAccessCodeModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<SetAccessCodeComponent.Params>()
|
||||
|
||||
internal val uiState: StateFlow<SetAccessCodeUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
fun onBack() {
|
||||
when (uiState.value.step) {
|
||||
SetAccessCodeUM.Step.AccessCode -> {
|
||||
params.callbacks.onBackClick()
|
||||
}
|
||||
SetAccessCodeUM.Step.ConfirmAccessCode -> {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
step = SetAccessCodeUM.Step.AccessCode,
|
||||
accessCodeSecond = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialState() = SetAccessCodeUM(
|
||||
step = SetAccessCodeUM.Step.AccessCode,
|
||||
accessCodeFirst = "",
|
||||
accessCodeSecond = "",
|
||||
onAccessCodeFirstChange = ::onAccessCodeFirstChange,
|
||||
onAccessCodeSecondChange = ::onAccessCodeSecondChange,
|
||||
buttonEnabled = false,
|
||||
onContinue = ::onContinue,
|
||||
)
|
||||
|
||||
private fun onAccessCodeFirstChange(value: String) {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
accessCodeFirst = value,
|
||||
buttonEnabled = value.length == uiState.value.accessCodeLength,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAccessCodeSecondChange(value: String) {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
accessCodeSecond = value,
|
||||
buttonEnabled = uiState.value.accessCodeFirst == uiState.value.accessCodeSecond,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onContinue() {
|
||||
when (uiState.value.step) {
|
||||
SetAccessCodeUM.Step.AccessCode -> {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
step = SetAccessCodeUM.Step.ConfirmAccessCode,
|
||||
)
|
||||
}
|
||||
}
|
||||
SetAccessCodeUM.Step.ConfirmAccessCode -> {
|
||||
params.callbacks.onAccessCodeSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.features.hotwallet.setaccesscode.entity
|
||||
|
||||
internal data class SetAccessCodeUM(
|
||||
val step: Step,
|
||||
val accessCodeFirst: String,
|
||||
val accessCodeSecond: String,
|
||||
val onAccessCodeFirstChange: (String) -> Unit,
|
||||
val onAccessCodeSecondChange: (String) -> Unit,
|
||||
val buttonEnabled: Boolean,
|
||||
val onContinue: () -> Unit,
|
||||
) {
|
||||
val accessCodeLength: Int = ACCESS_CODE_LENGTH
|
||||
|
||||
enum class Step {
|
||||
AccessCode,
|
||||
ConfirmAccessCode,
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ACCESS_CODE_LENGTH = 6
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.features.hotwallet.setaccesscode.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemAnimations
|
||||
import com.tangem.features.hotwallet.setaccesscode.entity.SetAccessCodeUM
|
||||
import com.tangem.features.hotwallet.setaccesscode.entity.SetAccessCodeUM.Step.*
|
||||
import com.tangem.core.res.R
|
||||
|
||||
@Composable
|
||||
internal fun SetAccessCodeContent(state: SetAccessCodeUM, onBack: () -> Unit, modifier: Modifier = Modifier) {
|
||||
BackHandler(onBack = onBack)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
AnimatedContent(
|
||||
modifier = Modifier.weight(1f),
|
||||
targetState = state.step,
|
||||
transitionSpec = TangemAnimations.AnimatedContent
|
||||
.slide { initial, target -> target.ordinal > initial.ordinal },
|
||||
label = "AnimatedContent",
|
||||
) { step ->
|
||||
when (step) {
|
||||
AccessCode -> SetAccessCodeEnter(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
state = state,
|
||||
reEnterAccessCodeState = false,
|
||||
)
|
||||
ConfirmAccessCode -> SetAccessCodeEnter(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
state = state,
|
||||
reEnterAccessCodeState = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.imePadding(),
|
||||
text = if (state.step == ConfirmAccessCode) {
|
||||
stringResourceSafe(R.string.common_confirm)
|
||||
} else {
|
||||
stringResourceSafe(R.string.common_continue)
|
||||
},
|
||||
onClick = state.onContinue,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.tangem.features.hotwallet.setaccesscode.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
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.res.R
|
||||
import com.tangem.core.ui.components.fields.PinTextField
|
||||
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.setaccesscode.entity.SetAccessCodeUM
|
||||
|
||||
@Composable
|
||||
internal fun SetAccessCodeEnter(
|
||||
state: SetAccessCodeUM,
|
||||
reEnterAccessCodeState: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(top = 56.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
text = if (reEnterAccessCodeState) {
|
||||
stringResourceSafe(R.string.access_code_confirm_title)
|
||||
} else {
|
||||
stringResourceSafe(R.string.access_code_create_title)
|
||||
},
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
text = if (reEnterAccessCodeState) {
|
||||
stringResourceSafe(R.string.access_code_confirm_description)
|
||||
} else {
|
||||
stringResourceSafe(
|
||||
R.string.access_code_create_description,
|
||||
state.accessCodeLength,
|
||||
)
|
||||
},
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
PinTextField(
|
||||
length = state.accessCodeLength,
|
||||
isPasswordVisual = true,
|
||||
value = if (reEnterAccessCodeState) {
|
||||
state.accessCodeSecond
|
||||
} else {
|
||||
state.accessCodeFirst
|
||||
},
|
||||
onValueChange = if (reEnterAccessCodeState) {
|
||||
state.onAccessCodeSecondChange
|
||||
} else {
|
||||
state.onAccessCodeFirstChange
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
SetAccessCodeEnter(
|
||||
reEnterAccessCodeState = false,
|
||||
state = SetAccessCodeUM(
|
||||
step = SetAccessCodeUM.Step.AccessCode,
|
||||
accessCodeFirst = "",
|
||||
accessCodeSecond = "",
|
||||
onAccessCodeFirstChange = {},
|
||||
onAccessCodeSecondChange = {},
|
||||
buttonEnabled = false,
|
||||
onContinue = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview2() {
|
||||
TangemThemePreview {
|
||||
SetAccessCodeEnter(
|
||||
reEnterAccessCodeState = true,
|
||||
state = SetAccessCodeUM(
|
||||
step = SetAccessCodeUM.Step.ConfirmAccessCode,
|
||||
accessCodeFirst = "",
|
||||
accessCodeSecond = "",
|
||||
onAccessCodeFirstChange = {},
|
||||
onAccessCodeSecondChange = {},
|
||||
buttonEnabled = false,
|
||||
onContinue = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.hotwallet.setupfinished
|
||||
|
||||
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.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.hotwallet.setupfinished.ui.MobileWalletSetupFinishedContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class MobileWalletSetupFinishedComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
private val model: MobileWalletSetupFinishedModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
MobileWalletSetupFinishedContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
interface ModelCallbacks {
|
||||
fun onContinueClick()
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val callbacks: ModelCallbacks,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.features.hotwallet.setupfinished
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.hotwallet.setupfinished.entity.MobileWalletSetupFinishedUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class MobileWalletSetupFinishedModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params: MobileWalletSetupFinishedComponent.Params = paramsContainer.require()
|
||||
|
||||
internal val uiState: StateFlow<MobileWalletSetupFinishedUM>
|
||||
field = MutableStateFlow(
|
||||
MobileWalletSetupFinishedUM(
|
||||
onContinueClick = params.callbacks::onContinueClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.hotwallet.setupfinished.entity
|
||||
|
||||
internal data class MobileWalletSetupFinishedUM(
|
||||
val onContinueClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.tangem.features.hotwallet.setupfinished.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.airbnb.lottie.compose.LottieAnimation
|
||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||
import com.airbnb.lottie.compose.animateLottieCompositionAsState
|
||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.FullScreen
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.features.hotwallet.setupfinished.entity.MobileWalletSetupFinishedUM
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun MobileWalletSetupFinishedContent(state: MobileWalletSetupFinishedUM, modifier: Modifier = Modifier) {
|
||||
val composition by rememberLottieComposition(spec = LottieCompositionSpec.RawRes(R.raw.anim_confetti))
|
||||
val progress by animateLottieCompositionAsState(composition)
|
||||
var showConfetti by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_success_blue_76),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 48.dp,
|
||||
top = 20.dp,
|
||||
end = 48.dp,
|
||||
),
|
||||
text = stringResourceSafe(R.string.onboarding_done_header),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
start = 48.dp,
|
||||
top = 12.dp,
|
||||
end = 48.dp,
|
||||
),
|
||||
text = stringResourceSafe(R.string.backup_complete_description),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(2f))
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.common_continue),
|
||||
showProgress = false,
|
||||
enabled = true,
|
||||
onClick = state.onContinueClick,
|
||||
)
|
||||
}
|
||||
|
||||
if (showConfetti) {
|
||||
FullScreen(notTouchable = true) {
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
progress = { progress },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
showConfetti = true
|
||||
}
|
||||
|
||||
LaunchedEffect(progress == 1f) {
|
||||
if (progress == 1f) {
|
||||
showConfetti = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewMobileWalletSetupFinishedContent() {
|
||||
TangemThemePreview {
|
||||
MobileWalletSetupFinishedContent(
|
||||
state = MobileWalletSetupFinishedUM(
|
||||
onContinueClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.hotwallet.walletbackup.component
|
||||
|
||||
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.WalletBackupComponent
|
||||
import com.tangem.features.hotwallet.walletbackup.model.WalletBackupModel
|
||||
import com.tangem.features.hotwallet.walletbackup.ui.WalletBackupContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultWalletBackupComponent @AssistedInject constructor(
|
||||
@Assisted private val context: AppComponentContext,
|
||||
@Assisted private val params: WalletBackupComponent.Params,
|
||||
) : WalletBackupComponent, AppComponentContext by context {
|
||||
|
||||
private val model: WalletBackupModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
WalletBackupContent(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : WalletBackupComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: WalletBackupComponent.Params,
|
||||
): DefaultWalletBackupComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.features.hotwallet.walletbackup.di
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.hotwallet.WalletBackupComponent
|
||||
import com.tangem.features.hotwallet.walletbackup.component.DefaultWalletBackupComponent
|
||||
import com.tangem.features.hotwallet.walletbackup.model.WalletBackupModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface WalletBackupModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(WalletBackupModel::class)
|
||||
fun bindWalletBackupModel(model: WalletBackupModel): Model
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindWalletBackupComponentFactory(factory: DefaultWalletBackupComponent.Factory): WalletBackupComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.features.hotwallet.walletbackup.entity
|
||||
|
||||
internal data class WalletBackupUM(
|
||||
val onBackClick: () -> Unit,
|
||||
val recoveryPhraseStatus: BackupStatus,
|
||||
val googleDriveStatus: BackupStatus,
|
||||
val onRecoveryPhraseClick: () -> Unit,
|
||||
val onGoogleDriveClick: () -> Unit,
|
||||
)
|
||||
|
||||
internal sealed class BackupStatus {
|
||||
object Done : BackupStatus()
|
||||
object ComingSoon : BackupStatus()
|
||||
object NoBackup : BackupStatus()
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.features.hotwallet.walletbackup.model
|
||||
|
||||
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.domain.wallets.usecase.GetUserWalletUseCase
|
||||
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 javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class WalletBackupModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
getWalletUseCase: GetUserWalletUseCase,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
) : Model() {
|
||||
|
||||
private val params: WalletBackupComponent.Params = paramsContainer.require()
|
||||
|
||||
val uiState: StateFlow<WalletBackupUM>
|
||||
field = MutableStateFlow(
|
||||
WalletBackupUM(
|
||||
onBackClick = { router.pop() },
|
||||
recoveryPhraseStatus = BackupStatus.NoBackup,
|
||||
googleDriveStatus = BackupStatus.ComingSoon,
|
||||
onRecoveryPhraseClick = { },
|
||||
onGoogleDriveClick = { },
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
getWalletUseCase.invokeFlow(params.userWalletId)
|
||||
.map { it.getOrNull() }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
updateBackupStatuses()
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun updateBackupStatuses() {
|
||||
uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
recoveryPhraseStatus = BackupStatus.NoBackup,
|
||||
googleDriveStatus = BackupStatus.ComingSoon,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package com.tangem.features.hotwallet.walletbackup.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
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.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
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.walletbackup.entity.BackupStatus
|
||||
import com.tangem.features.hotwallet.walletbackup.entity.WalletBackupUM
|
||||
import com.tangem.features.hotwallet.common.ui.OptionBlock
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.features.hotwallet.common.ui.DISABLED_COLORS_ALPHA
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun WalletBackupContent(state: WalletBackupUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.secondary)
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
AppBarWithBackButton(
|
||||
text = stringResourceSafe(R.string.common_backup),
|
||||
onBackClick = state.onBackClick,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
OptionBlock(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
title = stringResourceSafe(R.string.hw_backup_seed_title),
|
||||
description = stringResourceSafe(R.string.hw_backup_seed_description),
|
||||
badge = {
|
||||
BackupStatusBadge(status = state.recoveryPhraseStatus)
|
||||
},
|
||||
onClick = state.onRecoveryPhraseClick,
|
||||
enabled = true,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
|
||||
OptionBlock(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
title = stringResourceSafe(R.string.hw_backup_google_drive_title),
|
||||
description = stringResourceSafe(R.string.hw_backup_google_drive_description),
|
||||
badge = {
|
||||
BackupStatusBadge(status = state.googleDriveStatus)
|
||||
},
|
||||
onClick = state.onGoogleDriveClick,
|
||||
enabled = state.googleDriveStatus != BackupStatus.ComingSoon,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BackupStatusBadge(status: BackupStatus, modifier: Modifier = Modifier) {
|
||||
val text = when (status) {
|
||||
BackupStatus.Done -> stringResourceSafe(R.string.common_done)
|
||||
BackupStatus.ComingSoon -> stringResourceSafe(R.string.common_coming_soon)
|
||||
BackupStatus.NoBackup -> stringResourceSafe(R.string.hw_backup_no_backup)
|
||||
}
|
||||
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = when (status) {
|
||||
BackupStatus.Done -> TangemTheme.colors.text.accent.copy(alpha = 0.1f)
|
||||
BackupStatus.ComingSoon -> TangemTheme.colors.control.unchecked.copy(DISABLED_COLORS_ALPHA)
|
||||
BackupStatus.NoBackup -> TangemTheme.colors.text.warning.copy(alpha = 0.1f)
|
||||
},
|
||||
)
|
||||
|
||||
val textColor by animateColorAsState(
|
||||
targetValue = when (status) {
|
||||
BackupStatus.Done -> TangemTheme.colors.text.accent
|
||||
BackupStatus.ComingSoon -> TangemTheme.colors.text.secondary.copy(DISABLED_COLORS_ALPHA)
|
||||
BackupStatus.NoBackup -> TangemTheme.colors.text.warning
|
||||
},
|
||||
)
|
||||
|
||||
AnimatedContent(targetState = text) { text ->
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.background(
|
||||
color = backgroundColor,
|
||||
shape = TangemTheme.shapes.roundedCorners8,
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = textColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun WalletBackupContentPreview(@PreviewParameter(WalletBackupUMProvider::class) state: WalletBackupUM) {
|
||||
TangemThemePreview {
|
||||
WalletBackupContent(state)
|
||||
}
|
||||
}
|
||||
|
||||
private class WalletBackupUMProvider : CollectionPreviewParameterProvider<WalletBackupUM>(
|
||||
collection = listOf(
|
||||
WalletBackupUM(
|
||||
recoveryPhraseStatus = BackupStatus.NoBackup,
|
||||
googleDriveStatus = BackupStatus.ComingSoon,
|
||||
onBackClick = {},
|
||||
onRecoveryPhraseClick = {},
|
||||
onGoogleDriveClick = {},
|
||||
),
|
||||
WalletBackupUM(
|
||||
recoveryPhraseStatus = BackupStatus.NoBackup,
|
||||
googleDriveStatus = BackupStatus.NoBackup,
|
||||
onBackClick = {},
|
||||
onRecoveryPhraseClick = {},
|
||||
onGoogleDriveClick = {},
|
||||
),
|
||||
WalletBackupUM(
|
||||
recoveryPhraseStatus = BackupStatus.Done,
|
||||
googleDriveStatus = BackupStatus.Done,
|
||||
onBackClick = {},
|
||||
onRecoveryPhraseClick = {},
|
||||
onGoogleDriveClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -11,7 +11,7 @@ import com.sumsub.sns.core.theme.colors
|
|||
import com.sumsub.sns.core.theme.metrics
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.domain.pay.repository.KycRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.features.managetokens.component
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface AddCustomTokenComponent : ComposableBottomSheetComponent {
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.managetokens.component
|
|||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface ChooseManagedTokensComponent : ComposableContentComponent {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.features.managetokens.component
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface ManageTokensComponent : ComposableContentComponent {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.features.managetokens.component
|
|||
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface OnboardingManageTokensComponent : ComposableContentComponent {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.features.managetokens.choosetoken.entity
|
|||
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.features.managetokens.component
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableDialogComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
|
||||
internal interface CustomTokenDerivationInputComponent : ComposableDialogComponent {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.features.managetokens.component
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormValues
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.features.managetokens.component
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedDerivationPath
|
||||
import com.tangem.features.managetokens.entity.customtoken.SelectedNetwork
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.AddCustomTokenConfig
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorUM
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.features.managetokens.entity.customtoken
|
||||
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.features.managetokens.entity.customtoken
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.features.managetokens.entity.managetokens
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.managetokens.GetSupportedNetworksUseCase
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params.DerivationPathSelector
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent.Params.NetworkSelector
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.card.HasMissedDerivationsUseCase
|
||||
import com.tangem.domain.managetokens.SaveManagedTokensUseCase
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
|
||||
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.AddCustomTokenComponent
|
||||
import com.tangem.features.managetokens.component.preview.PreviewAddCustomTokenComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.AddCustomTokenConfig
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.component.preview.PreviewCustomTokenSelectorComponent
|
||||
import com.tangem.features.managetokens.entity.customtoken.CustomTokenSelectorUM
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import com.tangem.core.ui.utils.rememberHideKeyboardNestedScrollConnection
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.managetokens.component.preview.PreviewManageTokensComponent
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationE
|
|||
import com.tangem.domain.managetokens.model.exceptoin.FindTokenException
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveInAndJoin
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import com.tangem.core.ui.message.SnackbarMessage
|
|||
import com.tangem.domain.managetokens.*
|
||||
import com.tangem.domain.managetokens.model.*
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.managetokens.utils.list
|
|||
import com.tangem.domain.managetokens.model.ManageTokensListConfig
|
||||
import com.tangem.domain.managetokens.model.ManageTokensUpdateAction
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import com.tangem.pagination.Batch
|
||||
import com.tangem.pagination.BatchAction
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.managetokens.utils.list
|
|||
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
internal interface ManageTokensUiActions {
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dependencies {
|
|||
/* Project - API */
|
||||
api(projects.features.markets.api)
|
||||
api(projects.features.onramp.api)
|
||||
api(projects.features.sendV2.api)
|
||||
|
||||
/* Data */
|
||||
implementation(projects.data.common)
|
||||
|
|
@ -74,7 +75,6 @@ dependencies {
|
|||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.deepLinks)
|
||||
|
||||
/* Common */
|
||||
implementation(projects.common.ui)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.markets.deeplink
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.deeplink.DeeplinkConst.TOKEN_ID_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.TOKEN_ID_KEY
|
||||
import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import com.tangem.domain.tokens.error.TokenListError
|
|||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Portfolio data. Combined data from all flows that required to setup portfolio
|
||||
|
|
|
|||
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