Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-03 20:37:40 +02:00
commit da717845f0
538 changed files with 7350 additions and 5597 deletions

View file

@ -28,7 +28,7 @@ import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import arrow.core.getOrElse
import by.kirich1409.viewbindingdelegate.viewBinding
import com.arkivanov.decompose.value.observe
import com.arkivanov.decompose.value.subscribe
import com.arkivanov.essenty.lifecycle.asEssentyLifecycle
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
@ -46,6 +46,7 @@ import com.tangem.core.ui.message.EventMessageEffect
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
import com.tangem.data.card.sdk.CardSdkOwner
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.card.ScanCardUseCase
@ -59,14 +60,9 @@ import com.tangem.domain.staking.SendUnsubmittedHashesUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.google.GoogleServicesHelper
import com.tangem.operations.backup.BackupService
import com.tangem.sdk.api.BackupServiceHolder
@ -138,21 +134,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
lateinit var scanCardUseCase: ScanCardUseCase
@Inject
lateinit var walletRouter: WalletRouter
@Inject
lateinit var tokenDetailsRouter: TokenDetailsRouter
@Inject
lateinit var walletConnectInteractor: WalletConnectInteractor
@Inject
lateinit var sendRouter: SendRouter
@Inject
lateinit var qrScanningRouter: QrScanningRouter
@Inject
lateinit var deepLinksRegistry: DeepLinksRegistry
@ -187,9 +171,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
internal lateinit var routingComponentFactory: RoutingComponent.Factory
@Inject
lateinit var pushNotificationsRouter: PushNotificationsRouter
@Inject
lateinit var cardRepository: CardRepository
@ -217,6 +198,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
@Inject
internal lateinit var uiDependencies: UiDependencies
@Inject
internal lateinit var defaultDeviceFlipDetector: DefaultDeviceFlipDetector
internal val viewModel: MainViewModel by viewModels()
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
@ -283,6 +267,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
}
lifecycle.addObserver(WindowObscurationObserver)
lifecycle.addObserver(defaultDeviceFlipDetector)
}
private fun installEventMessageEffect() {
@ -331,12 +316,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
appRouterConfig.componentRouter = routingComponent.router
appRouterConfig.snackbarHandler = this
routingComponent.stack.observe(lifecycle.asEssentyLifecycle()) { childStack ->
routingComponent.stack.subscribe(lifecycle.asEssentyLifecycle()) { childStack ->
val stack = childStack.backStack
.plus(childStack.active)
.map { it.configuration }
if (stack == appRouterConfig.stack) return@observe
if (stack == appRouterConfig.stack) return@subscribe
appRouterConfig.stack = stack

View file

@ -1,152 +0,0 @@
package com.tangem.tap.common.compose
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.material.TextFieldColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.res.TangemTheme
// Copy from com.tangem.core.ui.components
// TODO: Delete this after fields has been moved to core-ui module
internal object TangemTextFieldsDefault {
val defaultTextFieldColors: TangemTextFieldColors
@Composable @Stable get() = TangemTextFieldColors(
textColor = TangemTheme.colors.text.primary1,
disabledTextColor = TangemTheme.colors.text.disabled,
backgroundColor = Color.Transparent,
cursorColor = TangemTheme.colors.icon.primary1,
errorCursorColor = TangemTheme.colors.icon.warning,
focusedIndicatorColor = TangemTheme.colors.icon.primary1,
unfocusedIndicatorColor = TangemTheme.colors.stroke.primary,
disabledIndicatorColor = TangemTheme.colors.stroke.primary,
errorIndicatorColor = TangemTheme.colors.icon.warning,
leadingIconColor = TangemTheme.colors.icon.informative,
disabledLeadingIconColor = Color.Transparent,
errorLeadingIconColor = TangemTheme.colors.icon.warning,
trailingIconColor = TangemTheme.colors.icon.informative,
disabledTrailingIconColor = Color.Transparent,
errorTrailingIconColor = TangemTheme.colors.icon.warning,
focusedLabelColor = TangemTheme.colors.text.primary1,
unfocusedLabelColor = TangemTheme.colors.text.secondary,
disabledLabelColor = TangemTheme.colors.text.disabled,
errorLabelColor = TangemTheme.colors.icon.warning,
placeholderColor = TangemTheme.colors.text.secondary,
disabledPlaceholderColor = TangemTheme.colors.text.disabled,
captionColor = TangemTheme.colors.text.tertiary,
disabledCaptionColor = TangemTheme.colors.text.disabled,
errorCaptionColor = TangemTheme.colors.icon.warning,
)
}
@Immutable
internal data class TangemTextFieldColors(
private val textColor: Color,
private val disabledTextColor: Color,
private val cursorColor: Color,
private val errorCursorColor: Color,
private val focusedIndicatorColor: Color,
private val unfocusedIndicatorColor: Color,
private val errorIndicatorColor: Color,
private val disabledIndicatorColor: Color,
private val leadingIconColor: Color,
private val disabledLeadingIconColor: Color,
private val errorLeadingIconColor: Color,
private val trailingIconColor: Color,
private val disabledTrailingIconColor: Color,
private val errorTrailingIconColor: Color,
private val backgroundColor: Color,
private val focusedLabelColor: Color,
private val unfocusedLabelColor: Color,
private val disabledLabelColor: Color,
private val errorLabelColor: Color,
private val placeholderColor: Color,
private val disabledPlaceholderColor: Color,
private val captionColor: Color,
private val disabledCaptionColor: Color,
private val errorCaptionColor: Color,
) : TextFieldColors {
@Composable
override fun leadingIconColor(enabled: Boolean, isError: Boolean): State<Color> {
return rememberUpdatedState(
when {
!enabled -> disabledLeadingIconColor
isError -> errorLeadingIconColor
else -> leadingIconColor
},
)
}
@Composable
override fun trailingIconColor(enabled: Boolean, isError: Boolean): State<Color> {
return rememberUpdatedState(
when {
!enabled -> disabledTrailingIconColor
isError -> errorTrailingIconColor
else -> trailingIconColor
},
)
}
@Composable
override fun indicatorColor(
enabled: Boolean,
isError: Boolean,
interactionSource: InteractionSource,
): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
!enabled -> disabledIndicatorColor
isError -> errorIndicatorColor
focused -> focusedIndicatorColor
else -> unfocusedIndicatorColor
}
return if (enabled) {
animateColorAsState(targetValue, tween(durationMillis = 120))
} else {
rememberUpdatedState(targetValue)
}
}
@Composable
override fun backgroundColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(backgroundColor)
}
@Composable
override fun placeholderColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(if (enabled) placeholderColor else disabledPlaceholderColor)
}
@Composable
override fun labelColor(enabled: Boolean, error: Boolean, interactionSource: InteractionSource): State<Color> {
val focused by interactionSource.collectIsFocusedAsState()
val targetValue = when {
!enabled -> disabledLabelColor
error -> errorLabelColor
focused -> focusedLabelColor
else -> unfocusedLabelColor
}
return rememberUpdatedState(targetValue)
}
@Composable
override fun textColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(if (enabled) textColor else disabledTextColor)
}
@Composable
override fun cursorColor(isError: Boolean): State<Color> {
return rememberUpdatedState(if (isError) errorCursorColor else cursorColor)
}
}

View file

@ -1,58 +0,0 @@
package com.tangem.tap.common.extensions
import android.view.View
import android.widget.EditText
import android.widget.TextSwitcher
import android.widget.TextView
import com.google.android.material.textfield.TextInputLayout
/**
[REDACTED_AUTHOR]
*/
fun TextView.update(text: String?) {
if (this.text?.toString() != text) this.text = text
}
fun EditText.update(text: String?) {
if (this.text?.toString() == text) return
val textLength = text?.length ?: 0
// prevent cursor jumping while editing a text
val cursorPosition = if (selectionEnd > textLength) textLength else selectionEnd
this.setText(text)
if (!isFocused || textLength == 0) return
if (cursorPosition == 0) setSelection(textLength) else setSelection(cursorPosition)
}
fun EditText.setOnImeActionListener(action: Int, handler: (EditText) -> Unit) {
this.setOnEditorActionListener { view, actionId, event ->
if (actionId == action) {
handler.invoke(this)
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
}
fun TextSwitcher.update(text: CharSequence?) {
val textView = this.currentView as? TextView ?: return
if (textView.text?.toString() != text?.toString()) this.setText(text)
}
// By default the TextInputLayout didn't activates the error state if the message is empty or null
fun TextInputLayout.enableError(enable: Boolean, errorMessage: String? = null) {
if (enable) {
if (errorMessage == null || errorMessage.isEmpty()) {
error = "Any message"
if (childCount == 2) getChildAt(1).visibility = View.GONE
} else {
error = errorMessage
}
} else {
error = null
isErrorEnabled = false
}
}

View file

@ -5,7 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
import com.arkivanov.decompose.defaultComponentContext
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.DefaultAppComponentContext
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.di.GlobalUiMessageSender
import com.tangem.core.decompose.di.RootAppComponentContext
import com.tangem.core.decompose.ui.UiMessageSender
@ -27,7 +27,7 @@ internal object RootAppComponentContextModule {
fun provideRootAppComponentContext(
@ActivityContext context: Context,
dispatchers: CoroutineDispatcherProvider,
componentBuilder: DecomposeComponent.Builder,
componentBuilder: ModelComponent.Builder,
@GlobalUiMessageSender messageSender: UiMessageSender,
): AppComponentContext {
return DefaultAppComponentContext(

View file

@ -5,10 +5,10 @@ import com.tangem.domain.analytics.repository.AnalyticsRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(ViewModelComponent::class)
@InstallIn(SingletonComponent::class)
internal object AnalyticsDomainModule {
@Provides

View file

@ -7,6 +7,8 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.domain.card.models.TwinKey
import com.tangem.domain.models.scan.isRing
import com.tangem.operations.sign.SignData
import com.tangem.tap.domain.tasks.MultiSignHashTask
import com.tangem.tap.domain.tasks.SignHashesTask
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
@ -64,6 +66,40 @@ class TangemSigner(
is CompletionResult.Failure -> CompletionResult.Failure(result.error)
}
}
override suspend fun multiSign(
dataToSign: List<SignData>,
publicKey: Wallet.PublicKey,
): CompletionResult<Map<ByteArray, ByteArray>> {
return suspendCancellableCoroutine { continuation ->
val task = MultiSignHashTask(dataToSign, publicKey, twinKey?.getPairKey(publicKey.seedKey))
tangemSdk.startSessionWithRunnable(
runnable = task,
cardId = cardId,
initialMessage = initialMessage,
) { result ->
when (result) {
is CompletionResult.Success -> {
signerCallback(
TangemSignerResponse(
totalSignedHashes = result.data.totalSignedHashes,
remainingSignatures = result.data.remainingSignatures,
isRing = result.data.batchId?.let(::isRing) ?: false,
),
)
if (continuation.isActive) {
continuation.resume(CompletionResult.Success(result.data.signatures))
}
}
is CompletionResult.Failure ->
if (continuation.isActive) {
continuation.resume(CompletionResult.Failure(result.error))
}
}
}
}
}
}
data class TangemSignerResponse(

View file

@ -490,6 +490,7 @@ internal class DefaultTangemSdkManager(
activationInput = activationInput,
coroutineScope = this,
),
cardId = activationInput.cardId,
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
)
}
@ -502,6 +503,7 @@ internal class DefaultTangemSdkManager(
runnable = VisaCustomerWalletApproveTask(
visaDataForApprove = visaDataForApprove,
),
cardId = visaDataForApprove.customerWalletCardId,
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
)
}

View file

@ -0,0 +1,80 @@
package com.tangem.tap.domain.tasks
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.CompletionCallback
import com.tangem.common.core.TangemSdkError
import com.tangem.operations.CommandResponse
import com.tangem.operations.sign.MultipleSignCommand
import com.tangem.operations.sign.SignData
class TangemMultiSignHashResponse(
val signatures: Map<ByteArray, ByteArray>,
val totalSignedHashes: Int?,
val remainingSignatures: Int?,
val batchId: String?,
) : CommandResponse
class MultiSignHashTask(
private val dataToSign: List<SignData>,
private val publicKey: Wallet.PublicKey,
private val pairWalletPublicKey: ByteArray?,
) : CardSessionRunnable<TangemMultiSignHashResponse> {
override fun run(session: CardSession, callback: CompletionCallback<TangemMultiSignHashResponse>) {
MultipleSignCommand(dataToSign, publicKey.seedKey).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
val card = session.environment.card
callback(
CompletionResult.Success(
TangemMultiSignHashResponse(
signatures = response.data.associate { it.walletPublicKey to it.signature },
totalSignedHashes = response.data.last().totalSignedHashes,
remainingSignatures = card?.wallet(publicKey.seedKey)?.remainingSignatures,
batchId = card?.batchId,
),
),
)
}
is CompletionResult.Failure -> {
when {
response.error is TangemSdkError.WalletNotFound && pairWalletPublicKey != null -> {
sign(session, pairWalletPublicKey, callback)
}
else -> callback(CompletionResult.Failure(response.error))
}
}
}
}
}
private fun sign(
session: CardSession,
publicKey: ByteArray,
callback: CompletionCallback<TangemMultiSignHashResponse>,
) {
MultipleSignCommand(dataToSign, publicKey).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
val card = session.environment.card
callback(
CompletionResult.Success(
TangemMultiSignHashResponse(
signatures = response.data.associate { it.walletPublicKey to it.signature },
totalSignedHashes = response.data.last().totalSignedHashes,
remainingSignatures = card?.wallet(publicKey)?.remainingSignatures,
batchId = card?.batchId,
),
),
)
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(response.error))
}
}
}
}
}

View file

@ -4,12 +4,15 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.wallets.models.UserWalletId
@JsonClass(generateAdapter = true)
internal data class UserWalletSensitiveInformation(
@Json(name = "wallets")
val wallets: List<CardDTO.Wallet>,
@Json(name = "visaCardActivationStatus")
val visaCardActivationStatus: VisaCardActivationStatus? = null,
)
@JsonClass(generateAdapter = true)

View file

@ -6,7 +6,10 @@ import com.tangem.tap.domain.userWalletList.model.UserWalletPublicInformation
import com.tangem.tap.domain.userWalletList.model.UserWalletSensitiveInformation
internal val UserWallet.sensitiveInformation: UserWalletSensitiveInformation
get() = UserWalletSensitiveInformation(scanResponse.card.wallets)
get() = UserWalletSensitiveInformation(
wallets = scanResponse.card.wallets,
visaCardActivationStatus = scanResponse.visaCardActivationStatus,
)
internal val UserWallet.publicInformation: UserWalletPublicInformation
get() = UserWalletPublicInformation(
@ -19,6 +22,7 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation
card = scanResponse.card.copy(
wallets = emptyList(),
),
visaCardActivationStatus = null,
),
hasBackupError = hasBackupError,
)
@ -45,6 +49,7 @@ internal fun UserWallet.updateWith(sensitiveInformation: UserWalletSensitiveInfo
card = scanResponse.card.copy(
wallets = sensitiveInformation.wallets,
),
visaCardActivationStatus = sensitiveInformation.visaCardActivationStatus,
),
)
}
@ -70,5 +75,6 @@ internal fun UserWallet.lock(): UserWallet = copy(
card = scanResponse.card.copy(
wallets = emptyList(),
),
visaCardActivationStatus = null,
),
)

View file

@ -1,9 +1,9 @@
package com.tangem.tap.features.details.redux
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.apptheme.model.AppThemeMode
import com.tangem.domain.models.scan.ScanResponse
import kotlinx.coroutines.CoroutineScope
import org.rekotlin.Action
sealed class DetailsAction : Action {
@ -27,7 +27,7 @@ sealed class DetailsAction : Action {
}
data class CheckBiometricsStatus(
val lifecycleScope: LifecycleCoroutineScope,
val coroutineScope: CoroutineScope,
) : AppSettings()
data object EnrollBiometrics : AppSettings()

View file

@ -1,6 +1,5 @@
package com.tangem.tap.features.details.redux
import androidx.lifecycle.LifecycleCoroutineScope
import com.tangem.common.CompletionResult
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
@ -22,6 +21,7 @@ import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
@ -67,7 +67,7 @@ class DetailsMiddleware {
}
}
is DetailsAction.AppSettings.CheckBiometricsStatus -> {
observeBiometricsStatusChanges(action.lifecycleScope)
observeBiometricsStatusChanges(action.coroutineScope)
}
is DetailsAction.AppSettings.EnrollBiometrics -> {
enrollBiometrics()
@ -90,7 +90,7 @@ class DetailsMiddleware {
}
}
private fun observeBiometricsStatusChanges(lifecycleScope: LifecycleCoroutineScope) {
private fun observeBiometricsStatusChanges(scope: CoroutineScope) {
val needEnrollBiometricsFlow = flow {
do {
val needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull()
@ -108,7 +108,7 @@ class DetailsMiddleware {
.onEach { needEnrollBiometrics ->
store.dispatchWithMain(DetailsAction.AppSettings.BiometricsStatusChanged(needEnrollBiometrics))
}
.launchIn(lifecycleScope)
.launchIn(scope)
.saveIn(checkBiometricsStatusJobHolder)
}

View file

@ -1,31 +0,0 @@
package com.tangem.tap.features.details.ui.appcurrency
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.core.ui.screen.ComposeFragment
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class AppCurrencySelectorFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
private val viewModel: AppCurrencySelectorViewModel by viewModels()
@Composable
override fun ScreenContent(modifier: Modifier) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
NavigationBar3ButtonsScrim()
AppCurrencySelectorScreen(
modifier = modifier,
state = uiState,
)
}
}

View file

@ -0,0 +1,38 @@
package com.tangem.tap.features.details.ui.appcurrency
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.components.NavigationBar3ButtonsScrim
import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent
import com.tangem.tap.features.details.ui.appcurrency.model.AppCurrencySelectorModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("UnusedPrivateMember")
internal class DefaultAppCurrencySelectorComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
) : AppCurrencySelectorComponent, AppComponentContext by appComponentContext {
private val model: AppCurrencySelectorModel = getOrCreateModel()
@Composable
override fun Content(modifier: Modifier) {
val uiState by model.uiState.collectAsStateWithLifecycle()
NavigationBar3ButtonsScrim()
AppCurrencySelectorScreen(
modifier = modifier,
state = uiState,
)
}
@AssistedFactory
interface Factory : AppCurrencySelectorComponent.Factory {
override fun create(context: AppComponentContext, params: Unit): DefaultAppCurrencySelectorComponent
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.tap.features.details.ui.appcurrency.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface AppCurrencySelectorComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Unit, AppCurrencySelectorComponent>
}

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.details.ui.appcurrency.di
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.details.ui.appcurrency.DefaultAppCurrencySelectorComponent
import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent
import com.tangem.tap.features.details.ui.appcurrency.model.AppCurrencySelectorModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface AppCurrencySelectorFeatureModule {
@Binds
fun bindFactory(impl: DefaultAppCurrencySelectorComponent.Factory): AppCurrencySelectorComponent.Factory
@Binds
@IntoMap
@ClassKey(AppCurrencySelectorModel::class)
fun bindModel(model: AppCurrencySelectorModel): Model
}

View file

@ -1,35 +1,38 @@
package com.tangem.tap.features.details.ui.appcurrency
package com.tangem.tap.features.details.ui.appcurrency.model
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.compose.runtime.Stable
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.appcurrency.GetAvailableCurrenciesUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.SelectAppCurrencyUseCase
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorIntents
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorState
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorStateHolder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
internal class AppCurrencySelectorViewModel @Inject constructor(
@Stable
@ModelScoped
internal class AppCurrencySelectorModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getAvailableCurrenciesUseCase: GetAvailableCurrenciesUseCase,
private val selectAppCurrencyUseCase: SelectAppCurrencyUseCase,
private val router: AppRouter,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), AppCurrencySelectorIntents {
) : Model(), AppCurrencySelectorIntents {
private val stateController = AppCurrencySelectorStateHolder(
intents = this,
onSubscription = { fetchCurrencies() },
stateFlowScope = viewModelScope,
stateFlowScope = modelScope,
)
val uiState: StateFlow<AppCurrencySelectorState> = stateController.stateFlow
@ -47,7 +50,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
}
override fun onCurrencyClick(currency: AppCurrencySelectorState.Currency) {
viewModelScope.launch(dispatchers.io) {
modelScope.launch(dispatchers.io) {
selectAppCurrencyUseCase(currency.id)
.onRight {
analyticsEventHandler.send(
@ -63,7 +66,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
}
private fun fetchCurrencies() {
viewModelScope.launch(dispatchers.io) {
modelScope.launch(dispatchers.io) {
val availableCurrencies = getAvailableCurrenciesUseCase()
.onRight(stateController::updateStateWithAvailableCurrencies)
.getOrNull()

View file

@ -1,42 +0,0 @@
package com.tangem.tap.features.details.ui.appsettings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class AppSettingsFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
@Inject
lateinit var appCurrencyRepository: AppCurrencyRepository
@Composable
override fun ScreenContent(modifier: Modifier) {
val viewModel = hiltViewModel<AppSettingsViewModel>().apply {
LocalLifecycleOwner.current.lifecycle.addObserver(observer = this)
}
val state by viewModel.uiState.collectAsStateWithLifecycle()
AppSettingsScreen(
modifier = modifier,
state = state,
onBackClick = {
store.dispatchNavigationAction(AppRouter::pop)
},
)
}
}

View file

@ -0,0 +1,49 @@
package com.tangem.tap.features.details.ui.appsettings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.essenty.lifecycle.doOnResume
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
import com.tangem.tap.features.details.ui.appsettings.model.AppSettingsModel
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("UnusedPrivateMember")
internal class DefaultAppSettingsComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
) : AppSettingsComponent, AppComponentContext by appComponentContext {
private val model: AppSettingsModel = getOrCreateModel()
init {
doOnResume { model.onResume() }
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
AppSettingsScreen(
modifier = modifier,
state = state,
onBackClick = {
store.dispatchNavigationAction(AppRouter::pop)
},
)
}
@AssistedFactory
interface Factory : AppSettingsComponent.Factory {
override fun create(context: AppComponentContext, params: Unit): DefaultAppSettingsComponent
}
}

View file

@ -2,13 +2,13 @@ package com.tangem.tap.features.details.ui.appsettings.analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState
import dagger.hilt.android.scopes.ViewModelScoped
import javax.inject.Inject
@ViewModelScoped
@ModelScoped
internal class AppSettingsItemsAnalyticsSender @Inject constructor(
private val analyticsHandler: AnalyticsEventHandler,
) {

View file

@ -0,0 +1,9 @@
package com.tangem.tap.features.details.ui.appsettings.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface AppSettingsComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Unit, AppSettingsComponent>
}

View file

@ -5,9 +5,8 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -20,7 +19,6 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun SettingsButtonItem(item: Item.Button, modifier: Modifier = Modifier) {
Surface(

View file

@ -2,10 +2,9 @@ package com.tangem.tap.features.details.ui.appsettings.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -22,7 +21,6 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun SettingsCardItem(item: Item.Card, modifier: Modifier = Modifier) {
Surface(

View file

@ -4,7 +4,7 @@ import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.details.ui.appsettings.di
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.details.ui.appsettings.DefaultAppSettingsComponent
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
import com.tangem.tap.features.details.ui.appsettings.model.AppSettingsModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface AppSettingsFeatureModule {
@Binds
fun bindFactory(impl: DefaultAppSettingsComponent.Factory): AppSettingsComponent.Factory
@Binds
@IntoMap
@ClassKey(AppSettingsModel::class)
fun bindModel(model: AppSettingsModel): Model
}

View file

@ -1,8 +1,10 @@
package com.tangem.tap.features.details.ui.appsettings
package com.tangem.tap.features.details.ui.appsettings.model
import androidx.lifecycle.*
import androidx.compose.runtime.Stable
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
import com.tangem.domain.apptheme.model.AppThemeMode
@ -20,12 +22,15 @@ import com.tangem.tap.features.details.redux.AppSetting
import com.tangem.tap.features.details.redux.AppSettingsState
import com.tangem.tap.features.details.redux.DetailsAction
import com.tangem.tap.features.details.redux.DetailsState
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory
import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState
import com.tangem.tap.features.details.ui.appsettings.analytics.AppSettingsItemsAnalyticsSender
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.*
@ -34,8 +39,10 @@ import org.rekotlin.StoreSubscriber
import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class AppSettingsViewModel @Inject constructor(
@Stable
@ModelScoped
internal class AppSettingsModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val appCurrencyRepository: AppCurrencyRepository,
private val walletsRepository: WalletsRepository,
private val canUseBiometryUseCase: CanUseBiometryUseCase,
@ -44,9 +51,7 @@ internal class AppSettingsViewModel @Inject constructor(
private val appThemeModeRepository: AppThemeModeRepository,
private val settingsRepository: SettingsRepository,
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
) : ViewModel(),
StoreSubscriber<DetailsState>,
DefaultLifecycleObserver {
) : Model(), StoreSubscriber<DetailsState> {
private val itemsFactory = AppSettingsItemsFactory()
private val dialogsFactory = AppSettingsDialogsFactory()
@ -82,11 +87,12 @@ internal class AppSettingsViewModel @Inject constructor(
}
}
override fun onResume(owner: LifecycleOwner) {
store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(owner.lifecycleScope))
fun onResume() {
store.dispatch(DetailsAction.AppSettings.CheckBiometricsStatus(modelScope))
}
override fun onCleared() {
override fun onDestroy() {
super.onDestroy()
store.unsubscribe(subscriber = this)
}
@ -225,7 +231,7 @@ internal class AppSettingsViewModel @Inject constructor(
.saveIn(appCurrencyUpdatesJobHolder)
}
private fun bootstrapBiometricsUpdates() = viewModelScope.launch {
private fun bootstrapBiometricsUpdates() = modelScope.launch {
val state = AppSettingsState(
saveWallets = walletsRepository.shouldSaveUserWalletsSync(),
saveAccessCodes = settingsRepository.shouldSaveAccessCodes(),

View file

@ -1,27 +0,0 @@
package com.tangem.tap.features.details.ui.cardsettings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class CardSettingsFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
private val viewModel: CardSettingsViewModel by viewModels()
@Composable
override fun ScreenContent(modifier: Modifier) {
val state by viewModel.screenState.collectAsStateWithLifecycle()
CardSettingsScreen(modifier = modifier, state = state)
}
}

View file

@ -8,7 +8,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate

View file

@ -0,0 +1,36 @@
package com.tangem.tap.features.details.ui.cardsettings
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.tap.features.details.ui.cardsettings.api.CardSettingsComponent
import com.tangem.tap.features.details.ui.cardsettings.model.CardSettingsModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultCardSettingsComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: CardSettingsComponent.Params,
) : CardSettingsComponent, AppComponentContext by appComponentContext {
private val model: CardSettingsModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.screenState.collectAsStateWithLifecycle()
CardSettingsScreen(modifier = modifier, state = state)
}
@AssistedFactory
interface Factory : CardSettingsComponent.Factory {
override fun create(
context: AppComponentContext,
params: CardSettingsComponent.Params,
): DefaultCardSettingsComponent
}
}

View file

@ -0,0 +1,14 @@
package com.tangem.tap.features.details.ui.cardsettings.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
interface CardSettingsComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
)
interface Factory : ComponentFactory<Params, CardSettingsComponent>
}

View file

@ -1,33 +0,0 @@
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
class AccessCodeRecoveryFragment : ComposeFragment() {
private val viewModel: AccessCodeRecoveryViewModel by viewModels()
@Inject
override lateinit var uiDependencies: UiDependencies
@Composable
override fun ScreenContent(modifier: Modifier) {
val state by viewModel.screenState.collectAsStateWithLifecycle()
AccessCodeRecoveryScreen(
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
)
}
}

View file

@ -0,0 +1,40 @@
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.model.AccessCodeRecoveryModel
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("UnusedPrivateMember")
internal class DefaultAccessCodeRecoveryComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
) : AccessCodeRecoveryComponent, AppComponentContext by appComponentContext {
private val model: AccessCodeRecoveryModel = getOrCreateModel()
@Composable
override fun Content(modifier: Modifier) {
val state by model.screenState.collectAsStateWithLifecycle()
AccessCodeRecoveryScreen(
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
)
}
@AssistedFactory
interface Factory : AccessCodeRecoveryComponent.Factory {
override fun create(context: AppComponentContext, params: Unit): DefaultAccessCodeRecoveryComponent
}
}

View file

@ -0,0 +1,9 @@
package com.tangem.tap.features.details.ui.cardsettings.coderecovery.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface AccessCodeRecoveryComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Unit, AccessCodeRecoveryComponent>
}

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.details.ui.cardsettings.coderecovery.di
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.DefaultAccessCodeRecoveryComponent
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.model.AccessCodeRecoveryModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface AccessCodeRecoveryFeatureModule {
@Binds
fun bindFactory(impl: DefaultAccessCodeRecoveryComponent.Factory): AccessCodeRecoveryComponent.Factory
@Binds
@IntoMap
@ClassKey(AccessCodeRecoveryModel::class)
fun bindModel(model: AccessCodeRecoveryModel): Model
}

View file

@ -1,29 +1,33 @@
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
package com.tangem.tap.features.details.ui.cardsettings.coderecovery.model
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.compose.runtime.Stable
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryScreenState
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled
import com.tangem.tap.store
import dagger.hilt.android.lifecycle.HiltViewModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
internal class AccessCodeRecoveryViewModel @Inject constructor(
@Stable
@ModelScoped
internal class AccessCodeRecoveryModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
private val cardSettingsInteractor: CardSettingsInteractor,
) : ViewModel() {
) : Model() {
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
?: error("Scan response is null")
@ -47,7 +51,7 @@ internal class AccessCodeRecoveryViewModel @Inject constructor(
)
}
private fun saveChanges() = viewModelScope.launch {
private fun saveChanges() = modelScope.launch {
val isEnabled = screenState.value.enabledSelection
tangemSdkManager

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.details.ui.cardsettings.di
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
import com.tangem.tap.features.details.ui.cardsettings.DefaultCardSettingsComponent
import com.tangem.tap.features.details.ui.cardsettings.model.CardSettingsModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface CardSettingsFeatureModule {
@Binds
fun bindFactory(impl: DefaultCardSettingsComponent.Factory): CardSettingsComponent.Factory
@Binds
@IntoMap
@ClassKey(CardSettingsModel::class)
fun bindModel(model: CardSettingsModel): Model
}

View file

@ -1,16 +1,15 @@
package com.tangem.tap.features.details.ui.cardsettings
package com.tangem.tap.features.details.ui.cardsettings.model
import android.os.Bundle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.bundle.unbundle
import com.tangem.core.analytics.Analytics
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.CardTypesResolver
@ -20,7 +19,6 @@ import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -29,13 +27,16 @@ import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.features.details.ui.cardsettings.CardInfo
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.*
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.wallet.R
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@ -43,22 +44,24 @@ import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class CardSettingsViewModel @Inject constructor(
@Stable
@ModelScoped
internal class CardSettingsModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val scanCardProcessor: ScanCardProcessor,
private val tangemSdkManager: TangemSdkManager,
private val cardSettingsInteractor: CardSettingsInteractor,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val settingsRepository: SettingsRepository,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
) : Model() {
private val params = paramsContainer.require<CardSettingsComponent.Params>()
private var previousBiometricsRequestPolicy: Boolean = false
private val userWalletId = savedStateHandle.get<Bundle>(AppRoute.CardSettings.USER_WALLET_ID_KEY)
?.unbundle(UserWalletId.serializer())
?: error("User wallet ID is required for CardSettingsViewModel")
private val userWalletId = params.userWalletId
val screenState: MutableStateFlow<CardSettingsScreenState> = MutableStateFlow(getInitialState())
@ -68,10 +71,11 @@ internal class CardSettingsViewModel @Inject constructor(
cardSettingsInteractor.scannedScanResponse
.filterNotNull()
.onEach(::updateCardDetails)
.launchIn(viewModelScope)
.launchIn(modelScope)
}
override fun onCleared() {
override fun onDestroy() {
super.onDestroy()
// Restore the previous value of access code request policy
cardSdkConfigRepository.isBiometricsRequestPolicy = previousBiometricsRequestPolicy
}
@ -96,7 +100,7 @@ internal class CardSettingsViewModel @Inject constructor(
onBackClick = ::onBackClick,
)
private fun scanCard() = viewModelScope.launch {
private fun scanCard() = modelScope.launch {
scanCardProcessor.scan(
analyticsSource = com.tangem.core.analytics.models.AnalyticsParam.ScreensSources.Settings,
allowsRequestAccessCodeFromRepository = true,
@ -217,7 +221,7 @@ internal class CardSettingsViewModel @Inject constructor(
}
}
private fun changeAccessCode() = viewModelScope.launch {
private fun changeAccessCode() = modelScope.launch {
val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) { "Scan response is null" }
when (val result = tangemSdkManager.setAccessCode(scanResponse.card.cardId)) {

View file

@ -0,0 +1,40 @@
package com.tangem.tap.features.details.ui.resetcard
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
import com.tangem.tap.features.details.ui.resetcard.model.ResetCardModel
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultResetCardComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: ResetCardComponent.Params,
) : ResetCardComponent, AppComponentContext by appComponentContext {
private val model: ResetCardModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.screenState.collectAsStateWithLifecycle()
ResetCardScreen(
modifier = modifier,
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
)
}
@AssistedFactory
interface Factory : ResetCardComponent.Factory {
override fun create(context: AppComponentContext, params: ResetCardComponent.Params): DefaultResetCardComponent
}
}

View file

@ -1,34 +0,0 @@
package com.tangem.tap.features.details.ui.resetcard
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class ResetCardFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
private val viewModel: ResetCardViewModel by viewModels()
@Composable
override fun ScreenContent(modifier: Modifier) {
val state by viewModel.screenState.collectAsStateWithLifecycle()
ResetCardScreen(
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
modifier = modifier,
)
}
}

View file

@ -0,0 +1,17 @@
package com.tangem.tap.features.details.ui.resetcard.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
interface ResetCardComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
val cardId: String,
val isActiveBackupStatus: Boolean,
val backupCardsCount: Int,
)
interface Factory : ComponentFactory<Params, ResetCardComponent>
}

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.details.ui.resetcard.di
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.details.ui.resetcard.DefaultResetCardComponent
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
import com.tangem.tap.features.details.ui.resetcard.model.ResetCardModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface ResetCardFeatureModule {
@Binds
fun bindFactory(impl: DefaultResetCardComponent.Factory): ResetCardComponent.Factory
@Binds
@IntoMap
@ClassKey(ResetCardModel::class)
fun bindModel(model: ResetCardModel): Model
}

View file

@ -1,21 +1,19 @@
package com.tangem.tap.features.details.ui.resetcard
package com.tangem.tap.features.details.ui.resetcard.model
import android.os.Bundle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
import com.tangem.domain.card.ResetCardUseCase
import com.tangem.domain.card.ResetCardUserCodeParams
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.asLockable
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -24,9 +22,12 @@ import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription
import com.tangem.tap.features.details.ui.resetcard.ResetCardDialog
import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
@ -35,8 +36,11 @@ import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class ResetCardViewModel @Inject constructor(
@Stable
@ModelScoped
internal class ResetCardModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
paramsContainer: ParamsContainer,
getUserWalletUseCase: GetUserWalletUseCase,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val resetCardUseCase: ResetCardUseCase,
@ -45,13 +49,12 @@ internal class ResetCardViewModel @Inject constructor(
private val userWalletsListManager: UserWalletsListManager,
private val analyticsEventHandler: AnalyticsEventHandler,
private val cardSettingsInteractor: CardSettingsInteractor,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
) : Model() {
private val params = paramsContainer.require<ResetCardComponent.Params>()
// region Card-set specific data. All cards from single set have the same userWalletId and cardTypesResolver
private val currentUserWalletId = savedStateHandle.get<Bundle>(AppRoute.ResetToFactory.USER_WALLET_ID)
?.unbundle(UserWalletId.serializer())
?: error("UserWalletId must be provided for ResetCardViewModel")
private val currentUserWalletId = params.userWalletId
// Use only for card-specific data
private val userWallet = getUserWalletUseCase(userWalletId = currentUserWalletId)
@ -65,15 +68,11 @@ internal class ResetCardViewModel @Inject constructor(
// endregion
// region Data of card that was scanned on CardSettings
private val primaryCardId: String = savedStateHandle.get<String>(AppRoute.ResetToFactory.CARD_ID)
?: error("CardId must be provided for ResetCardViewModel")
private val primaryCardId: String = params.cardId
private val isActiveBackupPrimaryCard =
savedStateHandle.get<Boolean>(AppRoute.ResetToFactory.IS_ACTIVE_BACKUP_STATUS)
?: error("IsActiveBackupCard must be provided for ResetCardViewModel")
private val isActiveBackupPrimaryCard = params.isActiveBackupStatus
private val primaryBackupCardsCount = savedStateHandle.get<Int>(AppRoute.ResetToFactory.BACKUP_CARDS_COUNT)
?: error("CardCount must be provided for ResetCardViewModel")
private val primaryBackupCardsCount = params.backupCardsCount
// endregion
// TODO: move logic to separate domain entity
@ -177,7 +176,7 @@ internal class ResetCardViewModel @Inject constructor(
}
private fun makeFullReset() {
viewModelScope.launch {
modelScope.launch {
resetCardUseCase(cardId = primaryCardId, params = currentUserCodeParams).onRight {
deleteSavedAccessCodesUseCase(cardId = primaryCardId)
val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse {
@ -203,7 +202,7 @@ internal class ResetCardViewModel @Inject constructor(
private fun onContinueResetClick() {
dismissDialog()
viewModelScope.launch {
modelScope.launch {
resetCardUseCase(
cardNumber = resetBackupCardCount + 1,
params = currentUserCodeParams,

View file

@ -0,0 +1,43 @@
package com.tangem.tap.features.details.ui.securitymode
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
import com.tangem.tap.features.details.ui.securitymode.model.SecurityModeModel
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class DefaultSecurityModeComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: SecurityModeComponent.Params,
) : SecurityModeComponent, AppComponentContext by appComponentContext {
private val model: SecurityModeModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.screenState.collectAsStateWithLifecycle()
SecurityModeScreen(
modifier = modifier,
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
)
}
@AssistedFactory
interface Factory : SecurityModeComponent.Factory {
override fun create(
context: AppComponentContext,
params: SecurityModeComponent.Params,
): DefaultSecurityModeComponent
}
}

View file

@ -1,34 +0,0 @@
package com.tangem.tap.features.details.ui.securitymode
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class SecurityModeFragment : ComposeFragment() {
@Inject
override lateinit var uiDependencies: UiDependencies
private val viewModel: SecurityModeViewModel by viewModels()
@Composable
override fun ScreenContent(modifier: Modifier) {
val state by viewModel.screenState.collectAsStateWithLifecycle()
SecurityModeScreen(
modifier = modifier,
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
)
}
}

View file

@ -0,0 +1,14 @@
package com.tangem.tap.features.details.ui.securitymode.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.wallets.models.UserWalletId
interface SecurityModeComponent : ComposableContentComponent {
data class Params(
val userWalletId: UserWalletId,
)
interface Factory : ComponentFactory<Params, SecurityModeComponent>
}

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.details.ui.securitymode.di
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.details.ui.securitymode.DefaultSecurityModeComponent
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
import com.tangem.tap.features.details.ui.securitymode.model.SecurityModeModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface SecurityModeFeatureModule {
@Binds
fun bindFactory(impl: DefaultSecurityModeComponent.Factory): SecurityModeComponent.Factory
@Binds
@IntoMap
@ClassKey(SecurityModeModel::class)
fun bindModel(model: SecurityModeModel): Model
}

View file

@ -1,11 +1,12 @@
package com.tangem.tap.features.details.ui.securitymode
package com.tangem.tap.features.details.ui.securitymode.model
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.compose.runtime.Stable
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
@ -15,18 +16,21 @@ import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.getAllowedSecurityOptions
import com.tangem.tap.features.details.ui.common.utils.getCurrentSecurityOption
import com.tangem.tap.features.details.ui.securitymode.SecurityModeScreenState
import com.tangem.tap.store
import dagger.hilt.android.lifecycle.HiltViewModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
internal class SecurityModeViewModel @Inject constructor(
@Stable
@ModelScoped
internal class SecurityModeModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
private val cardSettingsInteractor: CardSettingsInteractor,
) : ViewModel() {
) : Model() {
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
?: error("Scan response is null")
@ -62,7 +66,7 @@ internal class SecurityModeViewModel @Inject constructor(
val cardId = scannedScanResponse.card.cardId
val selectedOption = screenState.value.selectedSecurityMode
viewModelScope.launch {
modelScope.launch {
val result = when (selectedOption) {
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId)

View file

@ -0,0 +1,70 @@
package com.tangem.tap.features.details.ui.walletconnect
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.rekotlin.StoreSubscriber
@Suppress("UnusedPrivateMember")
internal class DefaultWalletConnectComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
) : WalletConnectComponent, AppComponentContext by appComponentContext, StoreSubscriber<WalletConnectState> {
private val model: WalletConnectModel = getOrCreateModel()
private var screenState: MutableState<WalletConnectScreenState> =
mutableStateOf(model.updateState(store.state.walletConnectState))
init {
lifecycle.subscribe(
onCreate = {
Analytics.send(WalletConnect.ScreenOpened())
},
onStart = {
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.walletConnectState == newState.walletConnectState
}.select { it.walletConnectState }
}
},
onStop = {
store.unsubscribe(this)
},
)
}
override fun newState(state: WalletConnectState) {
screenState.value = model.updateState(state)
}
@Composable
override fun Content(modifier: Modifier) {
WalletConnectScreen(
modifier = modifier,
state = screenState.value,
onBackClick = {
store.dispatchNavigationAction(AppRouter::pop)
},
)
}
@AssistedFactory
interface Factory : WalletConnectComponent.Factory {
override fun create(context: AppComponentContext, params: Unit): DefaultWalletConnectComponent
}
}

View file

@ -1,68 +0,0 @@
package com.tangem.tap.features.details.ui.walletconnect
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.Analytics
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.analytics.events.WalletConnect
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import org.rekotlin.StoreSubscriber
import javax.inject.Inject
@AndroidEntryPoint
internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<WalletConnectState> {
@Inject
override lateinit var uiDependencies: UiDependencies
private val viewModel: WalletConnectViewModel by viewModels()
private var screenState: MutableState<WalletConnectScreenState>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Analytics.send(WalletConnect.ScreenOpened())
lifecycle.addObserver(viewModel)
screenState = mutableStateOf(viewModel.updateState(store.state.walletConnectState))
}
@Composable
override fun ScreenContent(modifier: Modifier) {
val state = screenState?.value ?: return
WalletConnectScreen(
modifier = modifier,
state = state,
onBackClick = {
store.dispatchNavigationAction(AppRouter::pop)
},
)
}
override fun onStart() {
super.onStart()
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.walletConnectState == newState.walletConnectState
}.select { it.walletConnectState }
}
}
override fun onStop() {
super.onStop()
store.unsubscribe(this)
}
override fun newState(state: WalletConnectState) {
if (activity == null || view == null) return
screenState?.value = viewModel.updateState(state)
}
}

View file

@ -1,31 +1,34 @@
package com.tangem.tap.features.details.ui.walletconnect
import androidx.lifecycle.*
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
import com.tangem.tap.store
import dagger.hilt.android.lifecycle.HiltViewModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
internal class WalletConnectViewModel @Inject constructor(
@Stable
@ModelScoped
internal class WalletConnectModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
private val clipboardManager: ClipboardManager,
) : ViewModel(), DefaultLifecycleObserver {
) : Model() {
override fun onCreate(owner: LifecycleOwner) {
viewModelScope.launch {
init {
modelScope.launch {
listenToQrScanningUseCase(SourceType.WALLET_CONNECT)
.getOrElse { emptyFlow() }
.flowWithLifecycle(owner.lifecycle, minActiveState = Lifecycle.State.CREATED)
.collect { store.dispatch(WalletConnectAction.OpenSession(it)) }
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.tap.features.details.ui.walletconnect.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface WalletConnectComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Unit, WalletConnectComponent>
}

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.details.ui.walletconnect.di
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.details.ui.walletconnect.DefaultWalletConnectComponent
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectModel
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface WalletConnectFeatureModule {
@Binds
fun bindFactory(impl: DefaultWalletConnectComponent.Factory): WalletConnectComponent.Factory
@Binds
@IntoMap
@ClassKey(WalletConnectModel::class)
fun bindModel(model: WalletConnectModel): Model
}

View file

@ -0,0 +1,74 @@
package com.tangem.tap.features.home
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.arkivanov.essenty.lifecycle.subscribe
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.SystemBarsIconsDisposable
import com.tangem.core.ui.utils.findActivity
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.api.HomeComponent
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.rekotlin.StoreSubscriber
@Suppress("UnusedPrivateMember")
internal class DefaultHomeComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
) : HomeComponent, AppComponentContext by appComponentContext, StoreSubscriber<HomeState> {
private val model: HomeModel = getOrCreateModel()
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
init {
lifecycle.subscribe(
onCreate = {
store.dispatch(HomeAction.OnCreate)
},
onStart = {
store.subscribe(subscriber = this) { state ->
state
.skipRepeats { oldState, newState -> oldState.homeState == newState.homeState }
.select(AppState::homeState)
}
},
onStop = {
store.unsubscribe(this)
},
)
}
@Composable
override fun Content(modifier: Modifier) {
val activity = LocalContext.current.findActivity()
BackHandler(onBack = activity::finish)
SystemBarsIconsDisposable(darkIcons = false)
StoriesScreen(
homeState = homeState,
onScanButtonClick = model::onScanClick,
onShopButtonClick = model::onShopClick,
onSearchTokensClick = model::onSearchClick,
)
}
override fun newState(state: HomeState) {
homeState.value = state
}
@AssistedFactory
interface Factory : HomeComponent.Factory {
override fun create(context: AppComponentContext, params: Unit): DefaultHomeComponent
}
}

View file

@ -1,75 +0,0 @@
package com.tangem.tap.features.home
import android.os.Bundle
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.SystemBarsIconsDisposable
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.compose.StoriesScreen
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeState
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import org.rekotlin.StoreSubscriber
import javax.inject.Inject
@AndroidEntryPoint
internal class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
@Inject
override lateinit var uiDependencies: UiDependencies
private var homeState: MutableState<HomeState> = mutableStateOf(store.state.homeState)
private val viewModel by viewModels<HomeViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
store.dispatch(HomeAction.OnCreate)
}
@Composable
override fun ScreenContent(modifier: Modifier) {
BackHandler(onBack = requireActivity()::finish)
SystemBarsIconsDisposable(darkIcons = false)
ScreenContent()
}
override fun onStart() {
super.onStart()
store.subscribe(subscriber = this) { state ->
state
.skipRepeats { oldState, newState -> oldState.homeState == newState.homeState }
.select(AppState::homeState)
}
}
override fun onStop() {
super.onStop()
store.unsubscribe(this)
}
override fun newState(state: HomeState) {
if (activity == null || view == null) return
homeState.value = state
}
@Suppress("TopLevelComposableFunctions")
@Composable
private fun ScreenContent() {
StoriesScreen(
homeState = homeState,
onScanButtonClick = viewModel::onScanClick,
onShopButtonClick = viewModel::onShopClick,
onSearchTokensClick = viewModel::onSearchClick,
)
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.tap.features.home
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.compose.runtime.Stable
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.tangem.common.routing.AppRoute
@ -10,6 +9,8 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardSdkConfigRepository
@ -30,7 +31,7 @@ import com.tangem.tap.features.home.redux.HIDE_PROGRESS_DELAY
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeMiddleware.NEW_BUY_WALLET_URL
import com.tangem.tap.store
import dagger.hilt.android.lifecycle.HiltViewModel
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -38,8 +39,10 @@ import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class HomeViewModel @Inject constructor(
@Stable
@ModelScoped
internal class HomeModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val scanCardProcessor: ScanCardProcessor,
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
private val saveWalletUseCase: SaveWalletUseCase,
@ -47,7 +50,7 @@ internal class HomeViewModel @Inject constructor(
private val settingsRepository: SettingsRepository,
private val urlOpener: UrlOpener,
private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel() {
) : Model() {
private val tangemErrorHandler = TangemTangemErrorsHandler(store)
@ -73,7 +76,7 @@ internal class HomeViewModel @Inject constructor(
}
private fun scanCard() {
viewModelScope.launch {
modelScope.launch {
cardSdkConfigRepository.isBiometricsRequestPolicy = settingsRepository.shouldSaveAccessCodes()
scanCardProcessor.scan(

View file

@ -0,0 +1,9 @@
package com.tangem.tap.features.home.api
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
interface HomeComponent : ComposableContentComponent {
interface Factory : ComponentFactory<Unit, HomeComponent>
}

View file

@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

View file

@ -7,7 +7,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.home.di
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.home.DefaultHomeComponent
import com.tangem.tap.features.home.HomeModel
import com.tangem.tap.features.home.api.HomeComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface HomeFeatureModule {
@Binds
fun bindFactory(impl: DefaultHomeComponent.Factory): HomeComponent.Factory
@Binds
@IntoMap
@ClassKey(HomeModel::class)
fun bindModel(model: HomeModel): Model
}

View file

@ -3,9 +3,9 @@ package com.tangem.tap.features.saveWallet.ui.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

View file

@ -1,6 +1,6 @@
package com.tangem.tap.features.welcome.di
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.tap.features.welcome.model.WelcomeModel
import dagger.Binds
@ -10,7 +10,7 @@ import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
@InstallIn(ModelComponent::class)
internal interface ModelModule {
@Binds

View file

@ -3,8 +3,8 @@ package com.tangem.tap.features.welcome.ui.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.Text
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

View file

@ -4,6 +4,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.lib.auth.ExpressAuthProvider
import kotlinx.coroutines.runBlocking
import java.util.UUID
@ -25,6 +26,17 @@ internal class DefaultExpressAuthProvider(
}
override fun getRefCode(): String {
val selectedUserWallet = userWalletsStore.selectedUserWalletOrNull ?: error("Can not get selected user wallet")
return when {
isRing(selectedUserWallet) -> "ring"
isChangeNow(selectedUserWallet) -> "ChangeNow"
isPartner(selectedUserWallet) -> "partner"
else -> ""
}
}
private fun isRing(selectedUserWallet: UserWallet): Boolean {
val addedWalletsWithRings = runBlocking {
appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY,
@ -32,9 +44,19 @@ internal class DefaultExpressAuthProvider(
)
}
val userWalletId = userWalletsStore.selectedUserWalletOrNull?.walletId?.stringValue
?: error("No user id provided")
return addedWalletsWithRings.contains(selectedUserWallet.walletId.stringValue)
}
return if (addedWalletsWithRings.contains(userWalletId)) "ring" else ""
private fun isChangeNow(selectedUserWallet: UserWallet): Boolean {
return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_CHANGENOW
}
private fun isPartner(selectedUserWallet: UserWallet): Boolean {
return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_PARTNER
}
private companion object {
const val BATCH_ID_CHANGENOW = "BB000013"
const val BATCH_ID_PARTNER = "AF990015"
}
}

View file

@ -1,19 +0,0 @@
package com.tangem.tap.network.auth
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.visa.model.getAuthHeader
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import javax.inject.Inject
internal class DefaultVisaAuthProvider @Inject constructor(
private val userWalletsListManager: UserWalletsListManager,
) : TangemVisaAuthProvider {
override suspend fun getAuthHeader(cardId: String): String {
val card = userWalletsListManager.userWalletsSync.firstOrNull { it.cardId == cardId }
val status = card?.scanResponse?.visaCardActivationStatus as? VisaCardActivationStatus.Activated
?: return "Error in the app!"
return status.visaAuthTokens.getAuthHeader()
}
}

View file

@ -1,21 +1,17 @@
package com.tangem.tap.network.auth.di
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.tap.network.auth.*
import com.tangem.tap.network.auth.DefaultAppVersionProvider
import com.tangem.tap.network.auth.DefaultAuthProvider
import com.tangem.tap.network.auth.DefaultExpressAuthProvider
import com.tangem.tap.network.auth.DefaultStakeKitAuthProvider
import com.tangem.tap.network.auth.DefaultVisaAuthProvider
import com.tangem.utils.version.AppVersionProvider
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -55,13 +51,4 @@ internal class AuthModule {
fun provideAppVersionProvider(): AppVersionProvider {
return DefaultAppVersionProvider()
}
}
@Module
@InstallIn(SingletonComponent::class)
internal interface AuthBindModule {
@Binds
@Singleton
fun bindVisaAuthProvider(authStorage: DefaultVisaAuthProvider): TangemVisaAuthProvider
}

View file

@ -153,6 +153,8 @@ internal val Blockchain.mercuryoNetwork: String?
Blockchain.Bitrock, Blockchain.BitrockTestnet -> null
Blockchain.Sonic, Blockchain.SonicTestnet -> null
Blockchain.ApeChain, Blockchain.ApeChainTestnet -> null
Blockchain.Scroll, Blockchain.ScrollTestnet -> null
Blockchain.ZkLinkNova, Blockchain.ZkLinkNovaTestnet -> null
Blockchain.KaspaTestnet -> null
}
}

View file

@ -154,5 +154,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
Bitrock, BitrockTestnet -> null
Sonic, SonicTestnet -> null
ApeChain, ApeChainTestnet -> null
Scroll, ScrollTestnet -> null
ZkLinkNova, ZkLinkNovaTestnet -> null
KaspaTestnet -> null
}

View file

@ -33,7 +33,6 @@ import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
@ -67,7 +66,6 @@ data class DaggerGraphState(
val urlOpener: UrlOpener? = null,
val shareManager: ShareManager? = null,
val appRouter: AppRouter? = null,
val pushNotificationsRouter: PushNotificationsRouter? = null,
val transactionSignerFactory: TransactionSignerFactory? = null,
val getUserCountryUseCase: GetUserCountryUseCase? = null,
val onrampFeatureToggles: OnrampFeatureToggles? = null,

View file

@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
import com.arkivanov.decompose.extensions.compose.stack.Children
import com.arkivanov.decompose.router.stack.ChildStack
import com.tangem.common.routing.AppRoute
import com.tangem.core.ui.UiDependencies

View file

@ -4,11 +4,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.observe
import com.arkivanov.decompose.value.subscribe
import com.arkivanov.essenty.backhandler.BackCallback
import com.arkivanov.essenty.lifecycle.doOnDestroy
import com.google.android.material.snackbar.Snackbar
@ -64,7 +64,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
appRouterConfig.componentRouter = router
appRouterConfig.snackbarHandler = this
stack.observe(lifecycle) { stack ->
stack.subscribe(lifecycle) { stack ->
val stackItems = stack.items.map { it.configuration }
if (appRouterConfig.stack != stackItems) {

View file

@ -2,8 +2,8 @@ package com.tangem.tap.routing.utils
import com.tangem.common.routing.AppRoute
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.feature.qrscanning.QrScanningRouter
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.qrscanning.QrScanningComponent
import com.tangem.feature.referral.api.ReferralComponent
import com.tangem.feature.stories.api.StoriesComponent
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.features.details.component.DetailsComponent
@ -13,21 +13,21 @@ import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
import com.tangem.features.onramp.component.*
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
import com.tangem.features.send.api.SendComponent
import com.tangem.features.swap.SwapComponent
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorFragment
import com.tangem.tap.features.details.ui.appsettings.AppSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsFragment
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryFragment
import com.tangem.tap.features.details.ui.resetcard.ResetCardFragment
import com.tangem.tap.features.details.ui.securitymode.SecurityModeFragment
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.features.tokendetails.TokenDetailsComponent
import com.tangem.features.wallet.WalletEntryComponent
import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent
import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
import com.tangem.tap.features.home.api.HomeComponent
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment
@ -58,14 +58,23 @@ internal class ChildFactory @Inject constructor(
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
private val welcomeComponentFactory: WelcomeComponent.Factory,
private val storiesComponentFactory: StoriesComponent.Factory,
private val sendComponentFactory: SendComponent.Factory,
private val stakingComponentFactory: StakingComponent.Factory,
private val swapComponentFactory: SwapComponent.Factory,
private val sendRouter: SendRouter,
private val tokenDetailsRouter: TokenDetailsRouter,
private val walletRouter: WalletRouter,
private val qrScanningRouter: QrScanningRouter,
private val homeComponentFactory: HomeComponent.Factory,
private val tokenDetailsComponentFactory: TokenDetailsComponent.Factory,
private val walletConnectComponentFactory: WalletConnectComponent.Factory,
private val qrScanningComponentFactory: QrScanningComponent.Factory,
private val accessCodeRecoveryComponentFactory: AccessCodeRecoveryComponent.Factory,
private val cardSettingsComponentFactory: CardSettingsComponent.Factory,
private val appCurrencySelectorComponentFactory: AppCurrencySelectorComponent.Factory,
private val appSettingsComponentFactory: AppSettingsComponent.Factory,
private val securityModeComponentFactory: SecurityModeComponent.Factory,
private val resetCardComponentFactory: ResetCardComponent.Factory,
private val referralComponentFactory: ReferralComponent.Factory,
private val pushNotificationsComponentFactory: PushNotificationsComponent.Factory,
private val walletComponentFactory: WalletEntryComponent.Factory,
private val testerRouter: TesterRouter,
private val pushNotificationRouter: PushNotificationsRouter,
private val routingFeatureToggles: RoutingFeatureToggles,
) {
@ -213,6 +222,16 @@ internal class ChildFactory @Inject constructor(
componentFactory = storiesComponentFactory,
)
}
is AppRoute.CurrencyDetails -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = TokenDetailsComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
),
componentFactory = tokenDetailsComponentFactory,
)
}
is AppRoute.Staking -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
@ -237,25 +256,119 @@ internal class ChildFactory @Inject constructor(
componentFactory = swapComponentFactory,
)
}
is AppRoute.AccessCodeRecovery,
is AppRoute.AppCurrencySelector,
is AppRoute.Send -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SendComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
transactionId = route.transactionId,
amount = route.amount,
tag = route.tag,
destinationAddress = route.destinationAddress,
),
componentFactory = sendComponentFactory,
)
}
is AppRoute.Home -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = homeComponentFactory,
)
}
is AppRoute.WalletConnectSessions -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = walletConnectComponentFactory,
)
}
is AppRoute.QrScanning -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = QrScanningComponent.Params(
source = route.source,
networkName = route.networkName,
),
componentFactory = qrScanningComponentFactory,
)
}
is AppRoute.AccessCodeRecovery -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = accessCodeRecoveryComponentFactory,
)
}
is AppRoute.CardSettings -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = CardSettingsComponent.Params(userWalletId = route.userWalletId),
componentFactory = cardSettingsComponentFactory,
)
}
is AppRoute.AppCurrencySelector -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = appCurrencySelectorComponentFactory,
)
}
is AppRoute.AppSettings -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = appSettingsComponentFactory,
)
}
is AppRoute.DetailsSecurity -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SecurityModeComponent.Params(
userWalletId = route.userWalletId,
),
componentFactory = securityModeComponentFactory,
)
}
is AppRoute.ResetToFactory -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = ResetCardComponent.Params(
userWalletId = route.userWalletId,
cardId = route.cardId,
isActiveBackupStatus = route.isActiveBackupStatus,
backupCardsCount = route.backupCardsCount,
),
componentFactory = resetCardComponentFactory,
)
}
is AppRoute.ReferralProgram -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = ReferralComponent.Params(route.userWalletId),
componentFactory = referralComponentFactory,
)
}
is AppRoute.PushNotification -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = pushNotificationsComponentFactory,
)
}
is AppRoute.Wallet -> {
createComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = walletComponentFactory,
)
}
is AppRoute.SaveWallet,
is AppRoute.Send,
is AppRoute.AppSettings,
is AppRoute.CardSettings,
is AppRoute.DetailsSecurity,
is AppRoute.Home,
is AppRoute.OnboardingNote,
is AppRoute.OnboardingOther,
is AppRoute.OnboardingTwins,
is AppRoute.OnboardingWallet,
is AppRoute.QrScanning,
is AppRoute.ReferralProgram,
is AppRoute.ResetToFactory,
is AppRoute.Wallet,
is AppRoute.WalletConnectSessions,
is AppRoute.CurrencyDetails,
is AppRoute.PushNotification,
-> error("Unsupported route: $route")
}
// endregion
@ -271,22 +384,49 @@ internal class ChildFactory @Inject constructor(
Child.Initial
}
is AppRoute.AccessCodeRecovery -> {
route.asFragmentChild(Provider { AccessCodeRecoveryFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = accessCodeRecoveryComponentFactory,
)
}
is AppRoute.AppCurrencySelector -> {
route.asFragmentChild(Provider { AppCurrencySelectorFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = appCurrencySelectorComponentFactory,
)
}
is AppRoute.SaveWallet -> {
route.asFragmentChild(Provider { SaveWalletBottomSheetFragment() })
}
is AppRoute.Send -> {
route.asFragmentChild(Provider { sendRouter.getEntryFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SendComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
transactionId = route.transactionId,
amount = route.amount,
tag = route.tag,
destinationAddress = route.destinationAddress,
),
componentFactory = sendComponentFactory,
)
}
is AppRoute.AppSettings -> {
route.asFragmentChild(Provider { AppSettingsFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = appSettingsComponentFactory,
)
}
is AppRoute.CardSettings -> {
route.asFragmentChild(Provider { CardSettingsFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = CardSettingsComponent.Params(userWalletId = route.userWalletId),
componentFactory = cardSettingsComponentFactory,
)
}
is AppRoute.Details -> {
route.asComponentChild(
@ -296,7 +436,13 @@ internal class ChildFactory @Inject constructor(
)
}
is AppRoute.DetailsSecurity -> {
route.asFragmentChild(Provider { SecurityModeFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = SecurityModeComponent.Params(
userWalletId = route.userWalletId,
),
componentFactory = securityModeComponentFactory,
)
}
is AppRoute.Disclaimer -> {
route.asComponentChild(
@ -306,7 +452,11 @@ internal class ChildFactory @Inject constructor(
)
}
is AppRoute.Home -> {
route.asFragmentChild(Provider { HomeFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = homeComponentFactory,
)
}
is AppRoute.ManageTokens -> {
val source = when (route.source) {
@ -334,13 +484,33 @@ internal class ChildFactory @Inject constructor(
route.asFragmentChild(Provider { OnboardingWalletFragment() })
}
is AppRoute.QrScanning -> {
route.asFragmentChild(Provider { qrScanningRouter.getEntryFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = QrScanningComponent.Params(
source = route.source,
networkName = route.networkName,
),
componentFactory = qrScanningComponentFactory,
)
}
is AppRoute.ReferralProgram -> {
route.asFragmentChild(Provider { ReferralFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = ReferralComponent.Params(route.userWalletId),
componentFactory = referralComponentFactory,
)
}
is AppRoute.ResetToFactory -> {
route.asFragmentChild(Provider { ResetCardFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = ResetCardComponent.Params(
userWalletId = route.userWalletId,
cardId = route.cardId,
isActiveBackupStatus = route.isActiveBackupStatus,
backupCardsCount = route.backupCardsCount,
),
componentFactory = resetCardComponentFactory,
)
}
is AppRoute.Swap -> {
route.asComponentChild(
@ -356,13 +526,28 @@ internal class ChildFactory @Inject constructor(
)
}
is AppRoute.Wallet -> {
route.asFragmentChild(Provider { walletRouter.getEntryFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = walletComponentFactory,
)
}
is AppRoute.WalletConnectSessions -> {
route.asFragmentChild(Provider { WalletConnectFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = walletConnectComponentFactory,
)
}
is AppRoute.CurrencyDetails -> {
route.asFragmentChild(Provider { tokenDetailsRouter.getEntryFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = TokenDetailsComponent.Params(
userWalletId = route.userWalletId,
currency = route.currency,
),
componentFactory = tokenDetailsComponentFactory,
)
}
is AppRoute.Welcome -> {
route.asFragmentChild(Provider { WelcomeFragment() })
@ -382,7 +567,11 @@ internal class ChildFactory @Inject constructor(
)
}
is AppRoute.PushNotification -> {
route.asFragmentChild(Provider { pushNotificationRouter.entryFragment() })
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = Unit,
componentFactory = pushNotificationsComponentFactory,
)
}
is AppRoute.WalletSettings -> {
route.asComponentChild(