Updated on 2026-08-14
This commit is contained in:
commit
4ff980a0cd
41 changed files with 627 additions and 239 deletions
|
|
@ -130,6 +130,11 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
preparedData = state.dialog.data,
|
||||
context = context,
|
||||
)
|
||||
is WalletConnectDialog.PairConnectErrorDialog -> SimpleAlertDialog.create(
|
||||
titleRes = R.string.wallet_connect_title,
|
||||
message = state.dialog.error.message,
|
||||
context = context,
|
||||
)
|
||||
is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context)
|
||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.redux.LegacyAction
|
|||
import com.tangem.domain.tokens.utils.convertToAmount
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
import com.tangem.tap.common.feedback.RateCanBeBetterEmail
|
||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -27,6 +28,12 @@ internal object LegacyMiddleware {
|
|||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is LegacyAction.SendEmailSupport -> {
|
||||
store.state.globalState.feedbackManager?.sendEmail(
|
||||
feedbackData = FeedbackEmail(),
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is LegacyAction.StartOnboardingProcess -> {
|
||||
store.dispatch(
|
||||
GlobalAction.Onboarding.Start(action.scanResponse, canSkipBackup = action.canSkipBackup),
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import com.tangem.common.core.TangemError
|
|||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
import com.tangem.domain.card.ResetCardUseCase
|
||||
import com.tangem.domain.card.ResetCardUserCodeParams
|
||||
import com.tangem.domain.card.models.ResetCardError
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
|
||||
|
|
@ -20,24 +20,25 @@ internal class DefaultResetCardUseCase(
|
|||
private val tangemSdkManager: TangemSdkManager,
|
||||
) : ResetCardUseCase {
|
||||
|
||||
override suspend fun invoke(card: CardDTO): Either<ResetCardError, Unit> = resourceScope {
|
||||
either {
|
||||
withUserCodeRequestPolicy(card)
|
||||
override suspend fun invoke(cardId: String, params: ResetCardUserCodeParams): Either<ResetCardError, Boolean> =
|
||||
resourceScope {
|
||||
either {
|
||||
withUserCodeRequestPolicy(params)
|
||||
|
||||
tangemSdkManager.resetToFactorySettings(
|
||||
cardId = card.cardId,
|
||||
allowsRequestAccessCodeFromRepository = true,
|
||||
).bind(raise = this)
|
||||
tangemSdkManager.resetToFactorySettings(
|
||||
cardId = cardId,
|
||||
allowsRequestAccessCodeFromRepository = true,
|
||||
).bind(raise = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun invoke(
|
||||
cardNumber: Int,
|
||||
card: CardDTO,
|
||||
params: ResetCardUserCodeParams,
|
||||
userWalletId: UserWalletId,
|
||||
): Either<ResetCardError, Unit> = resourceScope {
|
||||
): Either<ResetCardError, Boolean> = resourceScope {
|
||||
either {
|
||||
withUserCodeRequestPolicy(card)
|
||||
withUserCodeRequestPolicy(params)
|
||||
|
||||
tangemSdkManager.resetBackupCard(
|
||||
cardNumber = cardNumber,
|
||||
|
|
@ -46,11 +47,11 @@ internal class DefaultResetCardUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun ResourceScope.withUserCodeRequestPolicy(card: CardDTO) {
|
||||
private suspend fun ResourceScope.withUserCodeRequestPolicy(params: ResetCardUserCodeParams) {
|
||||
install(
|
||||
acquire = {
|
||||
val policyBeforeReset = tangemSdkManager.userCodeRequestPolicy
|
||||
requestMandatoryAccessCodeEntry(card)
|
||||
requestMandatoryAccessCodeEntry(params)
|
||||
|
||||
policyBeforeReset
|
||||
},
|
||||
|
|
@ -60,10 +61,10 @@ internal class DefaultResetCardUseCase(
|
|||
)
|
||||
}
|
||||
|
||||
private fun requestMandatoryAccessCodeEntry(card: CardDTO) {
|
||||
val type = if (card.isAccessCodeSet) {
|
||||
private fun requestMandatoryAccessCodeEntry(params: ResetCardUserCodeParams) {
|
||||
val type = if (params.isAccessCodeSet) {
|
||||
UserCodeType.AccessCode
|
||||
} else if (card.isPasscodeSet == true) {
|
||||
} else if (params.isPasscodeSet == true) {
|
||||
UserCodeType.Passcode
|
||||
} else {
|
||||
null
|
||||
|
|
@ -74,15 +75,14 @@ internal class DefaultResetCardUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private fun CompletionResult<*>.bind(raise: Raise<ResetCardError>) {
|
||||
private fun CompletionResult<Boolean>.bind(raise: Raise<ResetCardError>): Boolean {
|
||||
return when (this) {
|
||||
is CompletionResult.Failure -> {
|
||||
val domainError = error.mapToDomainError()
|
||||
|
||||
raise.raise(domainError)
|
||||
}
|
||||
is CompletionResult.Success -> { /* no-op */
|
||||
}
|
||||
is CompletionResult.Success -> data
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ interface TangemSdkManager {
|
|||
suspend fun resetToFactorySettings(
|
||||
cardId: String,
|
||||
allowsRequestAccessCodeFromRepository: Boolean,
|
||||
): CompletionResult<CardDTO>
|
||||
): CompletionResult<Boolean>
|
||||
|
||||
suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Unit>
|
||||
suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Boolean>
|
||||
|
||||
suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit>
|
||||
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ class DefaultTangemSdkManager(
|
|||
override suspend fun resetToFactorySettings(
|
||||
cardId: String,
|
||||
allowsRequestAccessCodeFromRepository: Boolean,
|
||||
): CompletionResult<CardDTO> {
|
||||
): CompletionResult<Boolean> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ResetToFactorySettingsTask(
|
||||
allowsRequestAccessCodeFromRepository = allowsRequestAccessCodeFromRepository,
|
||||
|
|
@ -210,10 +210,9 @@ class DefaultTangemSdkManager(
|
|||
cardId = cardId,
|
||||
initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)),
|
||||
)
|
||||
.map { CardDTO(it) }
|
||||
}
|
||||
|
||||
override suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Unit> {
|
||||
override suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Boolean> {
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ResetBackupCardTask(userWalletId),
|
||||
initialMessage = Message(
|
||||
|
|
|
|||
|
|
@ -87,12 +87,12 @@ class MockTangemSdkManager(
|
|||
override suspend fun resetToFactorySettings(
|
||||
cardId: String,
|
||||
allowsRequestAccessCodeFromRepository: Boolean,
|
||||
): CompletionResult<CardDTO> {
|
||||
return MockProvider.getCardDto()
|
||||
): CompletionResult<Boolean> {
|
||||
return CompletionResult.Success(true)
|
||||
}
|
||||
|
||||
override suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
override suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Boolean> {
|
||||
return CompletionResult.Success(true)
|
||||
}
|
||||
|
||||
override suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit> {
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
|
|||
*/
|
||||
internal class ResetBackupCardTask(
|
||||
private val userWalletId: UserWalletId,
|
||||
) : CardSessionRunnable<Unit> {
|
||||
) : CardSessionRunnable<Boolean> {
|
||||
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean = false
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<Unit>) {
|
||||
override fun run(session: CardSession, callback: CompletionCallback<Boolean>) {
|
||||
PreflightReadTask(
|
||||
readMode = PreflightReadMode.FullCardRead,
|
||||
filter = UserWalletIdPreflightReadFilter(expectedUserWalletId = userWalletId),
|
||||
|
|
@ -35,10 +35,10 @@ internal class ResetBackupCardTask(
|
|||
}
|
||||
}
|
||||
|
||||
private fun resetCard(session: CardSession, callback: CompletionCallback<Unit>) {
|
||||
private fun resetCard(session: CardSession, callback: CompletionCallback<Boolean>) {
|
||||
ResetToFactorySettingsTask(allowsRequestAccessCodeFromRepository).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> callback(CompletionResult.Success(Unit))
|
||||
is CompletionResult.Success -> callback(CompletionResult.Success(result.data))
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,15 @@ import com.tangem.operations.wallet.PurgeWalletCommand
|
|||
|
||||
class ResetToFactorySettingsTask(
|
||||
override val allowsRequestAccessCodeFromRepository: Boolean,
|
||||
) : CardSessionRunnable<Card> {
|
||||
) : CardSessionRunnable<Boolean> {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
private var isResetCompleted = false
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Boolean>) -> Unit) {
|
||||
deleteWallets(session, callback)
|
||||
}
|
||||
|
||||
private fun deleteWallets(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
private fun deleteWallets(session: CardSession, callback: (result: CompletionResult<Boolean>) -> Unit) {
|
||||
val wallet = session.environment.card?.wallets?.lastOrNull().guard {
|
||||
resetBackup(session, callback)
|
||||
return
|
||||
|
|
@ -25,6 +27,7 @@ class ResetToFactorySettingsTask(
|
|||
PurgeWalletCommand(wallet.publicKey).run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
isResetCompleted = true
|
||||
deleteWallets(session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
|
|
@ -32,18 +35,18 @@ class ResetToFactorySettingsTask(
|
|||
}
|
||||
}
|
||||
|
||||
private fun resetBackup(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
private fun resetBackup(session: CardSession, callback: (result: CompletionResult<Boolean>) -> Unit) {
|
||||
if (session.environment.card?.backupStatus == null ||
|
||||
session.environment.card?.backupStatus == Card.BackupStatus.NoBackup
|
||||
) {
|
||||
callback(CompletionResult.Success(session.environment.card!!))
|
||||
callback(CompletionResult.Success(isResetCompleted))
|
||||
return
|
||||
}
|
||||
|
||||
ResetBackupCommand().run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(session.environment.card!!))
|
||||
callback(CompletionResult.Success(true))
|
||||
}
|
||||
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,21 +48,33 @@ internal class GeneralUserWalletsListManager(
|
|||
get() = requireImplementation.isLockable
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = implementation.transformLatest { impl ->
|
||||
if (impl != null && impl.hasUserWallets) {
|
||||
emitAll(impl.userWallets)
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.userWallets)
|
||||
}
|
||||
}
|
||||
}
|
||||
// To avoid returning empty flow to subscriber while implementation and userWallets are null
|
||||
// Flow is called first time when implementation is null and then when its assigned with implementation
|
||||
// that may have not user wallets (null or empty).
|
||||
// As a result subscription occurs on empty flow, than will not change if user wallets are available
|
||||
.filter { requireImplementation.hasUserWallets }
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = requireImplementation.userWalletsSync
|
||||
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = implementation.transformLatest { impl ->
|
||||
if (impl != null && impl.hasUserWallets) {
|
||||
emitAll(impl.selectedUserWallet)
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.selectedUserWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
// To avoid returning empty flow to subscriber while implementation and userWallets are null
|
||||
// Flow is called first time when implementation is null and then when its assigned with implementation
|
||||
// that may have not user wallets (null or empty).
|
||||
// As a result subscription occurs on empty flow, than will not change if user wallets are available
|
||||
.filter { requireImplementation.hasUserWallets }
|
||||
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = requireImplementation.selectedUserWalletSync
|
||||
|
|
|
|||
|
|
@ -46,4 +46,8 @@ internal class WalletConnectEventsHandlerImpl : WalletConnectEventsHandler {
|
|||
override fun onUnsupportedRequest() {
|
||||
store.dispatchOnMain(WalletConnectAction.RejectUnsupportedRequest)
|
||||
}
|
||||
|
||||
override fun onPairConnectError(error: Throwable) {
|
||||
store.dispatchOnMain(WalletConnectAction.PairConnectErrorAction(error))
|
||||
}
|
||||
}
|
||||
|
|
@ -246,7 +246,20 @@ internal class DefaultLegacyWalletConnectRepository(
|
|||
}
|
||||
|
||||
override fun pair(uri: String) {
|
||||
Web3Wallet.pair(Wallet.Params.Pair(uri))
|
||||
Web3Wallet.pair(
|
||||
params = Wallet.Params.Pair(uri),
|
||||
onSuccess = {
|
||||
Timber.i("Paired successfully: $it")
|
||||
},
|
||||
onError = {
|
||||
Timber.e("Error while pairing: $it")
|
||||
scope.launch {
|
||||
_events.emit(
|
||||
WalletConnectEvents.PairConnectError(it.throwable),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>) {
|
||||
|
|
|
|||
|
|
@ -14,26 +14,24 @@ import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
|||
import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl
|
||||
import com.tangem.tap.domain.walletconnect2.data.DefaultLegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.data.DefaultWalletConnectSessionsRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer
|
||||
import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object WalletConnectInteractorModule {
|
||||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
@Singleton
|
||||
fun provideWalletConnectInteractor(
|
||||
wcRepository: LegacyWalletConnectRepository,
|
||||
wcSessionsRepository: WalletConnectSessionsRepository,
|
||||
|
|
@ -41,6 +39,7 @@ internal object WalletConnectInteractorModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): WalletConnectInteractor {
|
||||
return WalletConnectInteractor(
|
||||
handler = WalletConnectEventsHandlerImpl(),
|
||||
|
|
@ -51,7 +50,7 @@ internal object WalletConnectInteractorModule {
|
|||
currenciesRepository = currenciesRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
dispatchers = coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,6 @@ interface WalletConnectEventsHandler {
|
|||
fun onSessionRequest(request: WcPreparedRequest)
|
||||
|
||||
fun onUnsupportedRequest()
|
||||
|
||||
fun onPairConnectError(error: Throwable)
|
||||
}
|
||||
|
|
@ -9,18 +9,18 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.filterNotNull
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.*
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.Stack
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList")
|
||||
class WalletConnectInteractor(
|
||||
|
|
@ -35,18 +35,27 @@ class WalletConnectInteractor(
|
|||
val blockchainHelper: WcBlockchainHelper,
|
||||
) {
|
||||
|
||||
var isWalletConnectReadyForDeepLinks = false
|
||||
|
||||
private val getSelectedWalletUseCase by lazy(LazyThreadSafetyMode.NONE) {
|
||||
GetSelectedWalletUseCase(userWalletsListManager)
|
||||
}
|
||||
|
||||
private val wcScope = CoroutineScope(
|
||||
Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("wcScope"),
|
||||
SupervisorJob() + dispatchers.io + CoroutineExceptionHandler { _, throwable ->
|
||||
Timber.e("CoroutineException: from: LISTENER SCOPE, exception: $throwable")
|
||||
},
|
||||
)
|
||||
|
||||
private val listenerScope = CoroutineScope(
|
||||
Job() + dispatchers.io + FeatureCoroutineExceptionHandler.create("listenScope"),
|
||||
SupervisorJob() + dispatchers.io + CoroutineExceptionHandler { _, throwable ->
|
||||
Timber.e("CoroutineException: from: LISTENER SCOPE, exception: $throwable")
|
||||
},
|
||||
)
|
||||
|
||||
/** Stack of deeplinks to handle if user wallet is not selected or cryptocurrency statuses are not available */
|
||||
private val deeplinkStack: Stack<String> = Stack()
|
||||
|
||||
private val events = walletConnectRepository.events
|
||||
private val sessions = walletConnectRepository.activeSessions
|
||||
|
||||
|
|
@ -96,6 +105,7 @@ class WalletConnectInteractor(
|
|||
networks = currencies.map { it.network },
|
||||
)
|
||||
setUserChains(accounts)
|
||||
handleDeeplinkStack(accounts)
|
||||
}
|
||||
|
||||
private suspend fun startListeningWc(userWalletId: String, cardId: String?) {
|
||||
|
|
@ -118,6 +128,17 @@ class WalletConnectInteractor(
|
|||
walletConnectRepository.setUserNamespaces(userNamespaces)
|
||||
}
|
||||
|
||||
private fun handleDeeplinkStack(accounts: List<Account>) {
|
||||
runCatching {
|
||||
if (accounts.isEmpty()) return
|
||||
isWalletConnectReadyForDeepLinks = true
|
||||
val lastDeeplink = deeplinkStack.pop()
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(lastDeeplink))
|
||||
}.onFailure {
|
||||
Timber.e("WC deeplink handling failed. $it")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun subscribeToEvents() {
|
||||
events
|
||||
.onEach { wcEvent ->
|
||||
|
|
@ -167,6 +188,9 @@ class WalletConnectInteractor(
|
|||
is WalletConnectEvents.SessionRequest -> {
|
||||
handleRequest(wcEvent)
|
||||
}
|
||||
is WalletConnectEvents.PairConnectError -> {
|
||||
handler.onPairConnectError(wcEvent.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
|
|
@ -329,6 +353,21 @@ class WalletConnectInteractor(
|
|||
return uri.lowercase().startsWith(WC_SCHEME)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Wallet Connect deep links.
|
||||
* If wallet connect is able to handle the deeplink, session is started with deeplink.
|
||||
* Otherwise, deeplink is stored until wallet connect is ready to handle it.
|
||||
*
|
||||
* @param deeplink deeplink to handle
|
||||
*/
|
||||
fun addDeeplink(deeplink: String) {
|
||||
if (isWalletConnectReadyForDeepLinks) {
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(deeplink))
|
||||
} else {
|
||||
deeplinkStack.push(deeplink)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCardId(userWallet: UserWallet): String? {
|
||||
return if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
|
||||
userWallet.cardId
|
||||
|
|
|
|||
|
|
@ -26,4 +26,6 @@ sealed interface WalletConnectEvents {
|
|||
val metaUrl: String,
|
||||
val method: String,
|
||||
) : WalletConnectEvents
|
||||
|
||||
data class PairConnectError(val error: Throwable) : WalletConnectEvents
|
||||
}
|
||||
|
|
@ -22,17 +22,19 @@ sealed class WalletConnectAction : Action {
|
|||
|
||||
data class ShowClipboardOrScanQrDialog(val wcUri: String) : WalletConnectAction()
|
||||
//region WalletConnect 2.0
|
||||
object ApproveProposal : WalletConnectAction()
|
||||
object RejectProposal : WalletConnectAction()
|
||||
data object ApproveProposal : WalletConnectAction()
|
||||
data object RejectProposal : WalletConnectAction()
|
||||
|
||||
object SessionEstablished : WalletConnectAction()
|
||||
data object SessionEstablished : WalletConnectAction()
|
||||
data class SessionRejected(val error: WalletConnectError) : WalletConnectAction()
|
||||
data class SessionListUpdated(val sessions: List<WcSessionForScreen>) : WalletConnectAction()
|
||||
|
||||
data class ShowSessionRequest(val sessionRequest: WcPreparedRequest) : WalletConnectAction()
|
||||
|
||||
object RejectUnsupportedRequest : WalletConnectAction()
|
||||
data object RejectUnsupportedRequest : WalletConnectAction()
|
||||
|
||||
data class PerformRequestedAction(val sessionRequest: WcPreparedRequest) : WalletConnectAction()
|
||||
|
||||
data class PairConnectErrorAction(val throwable: Throwable) : WalletConnectAction()
|
||||
//endregion WalletConnect 2.0
|
||||
}
|
||||
|
|
@ -47,8 +47,11 @@ class WalletConnectMiddleware {
|
|||
|
||||
when (action) {
|
||||
is WalletConnectAction.HandleDeepLink -> {
|
||||
if (!action.wcUri.isNullOrBlank()) {
|
||||
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
|
||||
val wsUrl = action.wcUri
|
||||
Timber.i("WC deeplink: $wsUrl")
|
||||
if (!wsUrl.isNullOrBlank()) {
|
||||
Timber.i("WC deeplink added to stack: $wsUrl")
|
||||
walletConnectInteractor.addDeeplink(wsUrl)
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.DisconnectSession -> {
|
||||
|
|
@ -172,6 +175,9 @@ class WalletConnectMiddleware {
|
|||
is WalletConnectAction.RejectUnsupportedRequest -> {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork()))
|
||||
}
|
||||
is WalletConnectAction.PairConnectErrorAction -> {
|
||||
store.dispatch(GlobalAction.ShowDialog(WalletConnectDialog.PairConnectErrorDialog(action.throwable)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ object WalletConnectReducer {
|
|||
is WalletConnectAction.RejectProposal,
|
||||
is WalletConnectAction.SessionEstablished,
|
||||
is WalletConnectAction.SessionRejected,
|
||||
is WalletConnectAction.PairConnectErrorAction,
|
||||
-> state.copy(loading = false)
|
||||
is WalletConnectAction.SessionListUpdated -> state.copy(
|
||||
wc2Sessions = action.sessions,
|
||||
|
|
|
|||
|
|
@ -117,6 +117,8 @@ sealed class WalletConnectDialog : StateDialog {
|
|||
data class SignTransactionDialog(
|
||||
val data: WcPreparedRequest.SignTransaction,
|
||||
) : WalletConnectDialog()
|
||||
|
||||
data class PairConnectErrorDialog(val error: Throwable) : WalletConnectDialog()
|
||||
}
|
||||
|
||||
data class WcTransactionData(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.os.Bundle
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRoute
|
||||
|
|
@ -14,10 +13,9 @@ import com.tangem.domain.card.ScanCardProcessor
|
|||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
|
|
@ -38,7 +36,6 @@ import javax.inject.Inject
|
|||
@HiltViewModel
|
||||
internal class CardSettingsViewModel @Inject constructor(
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
|
@ -47,6 +44,8 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
?.unbundle(UserWalletId.serializer())
|
||||
?: error("User wallet ID is required for CardSettingsViewModel")
|
||||
|
||||
private val scannedScanResponse = MutableStateFlow<ScanResponse?>(value = null)
|
||||
|
||||
val screenState: MutableStateFlow<CardSettingsScreenState> = MutableStateFlow(getInitialState())
|
||||
|
||||
private fun getInitialState() = CardSettingsScreenState(
|
||||
|
|
@ -58,13 +57,11 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
private fun scanCard() = viewModelScope.launch {
|
||||
scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true)
|
||||
.doOnSuccess { scanResponse ->
|
||||
scannedScanResponse.value = scanResponse
|
||||
|
||||
val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
val isCorrectUserWalletScanned = scannedUserWalletId == userWalletId
|
||||
|
||||
if (isCorrectUserWalletScanned) {
|
||||
val userWallet = getUserWallet()
|
||||
|
||||
updateCardDetails(userWallet)
|
||||
if (userWalletId == scannedUserWalletId || scannedUserWalletId == null) {
|
||||
updateCardDetails(scanResponse)
|
||||
} else {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
|
|
@ -76,9 +73,9 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateCardDetails(userWallet: UserWallet) {
|
||||
val card = userWallet.scanResponse.card
|
||||
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
|
||||
private fun updateCardDetails(scanResponse: ScanResponse) {
|
||||
val card = scanResponse.card
|
||||
val cardTypesResolver = scanResponse.cardTypesResolver
|
||||
val cardId = cardTypesResolver.getCardId()
|
||||
|
||||
val currentSecurityOption = getCurrentSecurityOption(card)
|
||||
|
|
@ -112,7 +109,7 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
|
||||
if (isResetCardAllowed) {
|
||||
CardInfo.ResetToFactorySettings(
|
||||
description = getResetToFactoryDescription(card, cardTypesResolver),
|
||||
description = getResetToFactoryDescription(card.backupStatus, cardTypesResolver),
|
||||
).let(::add)
|
||||
}
|
||||
}
|
||||
|
|
@ -129,9 +126,21 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
changeAccessCode()
|
||||
}
|
||||
is CardInfo.ResetToFactorySettings -> {
|
||||
val card = requireNotNull(scannedScanResponse.value) {
|
||||
"Impossible to reset card if ScanResponse is null"
|
||||
}.card
|
||||
|
||||
Analytics.send(Settings.CardSettings.ButtonFactoryReset())
|
||||
store.dispatchNavigationAction {
|
||||
push(route = AppRoute.ResetToFactory(userWalletId))
|
||||
push(
|
||||
route = AppRoute.ResetToFactory(
|
||||
userWalletId = userWalletId,
|
||||
cardSpecificInfo = AppRoute.ResetToFactory.CardSpecificInfo(
|
||||
cardId = card.cardId,
|
||||
backupStatus = card.backupStatus,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is CardInfo.SecurityMode -> {
|
||||
|
|
@ -150,9 +159,9 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun changeAccessCode() = viewModelScope.launch {
|
||||
val card = getUserWallet().scanResponse.card
|
||||
val scanResponse = requireNotNull(scannedScanResponse.value) { "Scan response is null" }
|
||||
|
||||
when (val result = tangemSdkManager.setAccessCode(card.cardId)) {
|
||||
when (val result = tangemSdkManager.setAccessCode(scanResponse.card.cardId)) {
|
||||
is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged())
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("Failed to change access code: ${result.error}")
|
||||
|
|
@ -160,12 +169,6 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getUserWallet(): UserWallet {
|
||||
return getUserWalletUseCase(userWalletId).getOrElse {
|
||||
error("Failed to get user wallet $userWalletId: $it")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean {
|
||||
val hasPermanentWallet = card.wallets.any { it.settings.isPermanent }
|
||||
val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin()
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import com.tangem.domain.models.scan.CardDTO
|
|||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal fun getResetToFactoryDescription(card: CardDTO, typesResolver: CardTypesResolver): TextReference {
|
||||
return if (card.backupStatus?.isActive != true || typesResolver.isTangemTwins()) {
|
||||
internal fun getResetToFactoryDescription(
|
||||
backupStatus: CardDTO.BackupStatus?,
|
||||
typesResolver: CardTypesResolver,
|
||||
): TextReference {
|
||||
return if (backupStatus?.isActive != true || typesResolver.isTangemTwins()) {
|
||||
TextReference.Res(R.string.reset_card_without_backup_to_factory_message)
|
||||
} else {
|
||||
TextReference.Res(R.string.reset_card_with_backup_to_factory_message)
|
||||
|
|
|
|||
|
|
@ -11,12 +11,11 @@ import com.tangem.common.routing.utils.popTo
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
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.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
|
|
@ -39,7 +38,7 @@ import javax.inject.Inject
|
|||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class ResetCardViewModel @Inject constructor(
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val resetCardUseCase: ResetCardUseCase,
|
||||
private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase,
|
||||
|
|
@ -49,9 +48,26 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private val userWalletId = savedStateHandle.get<Bundle>(AppRoute.ResetToFactory.USER_WALLET_ID)
|
||||
// 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("User wallet ID must be provided for ResetCardViewModel")
|
||||
?: error("UserWalletId must be provided for ResetCardViewModel")
|
||||
|
||||
// Use only for card-specific data
|
||||
private val userWallet = getUserWalletUseCase(userWalletId = currentUserWalletId)
|
||||
.getOrElse { error("Failed to get user wallet: $it") }
|
||||
|
||||
private val currentCardTypesResolver = userWallet.cardTypesResolver
|
||||
private val currentUserCodeParams = ResetCardUserCodeParams(
|
||||
isAccessCodeSet = userWallet.scanResponse.card.isAccessCodeSet,
|
||||
isPasscodeSet = userWallet.scanResponse.card.isPasscodeSet,
|
||||
)
|
||||
// endregion
|
||||
|
||||
// region Data of card that was scanned on CardSettings
|
||||
private val primaryCardId: String
|
||||
private val primaryBackupStatus: CardDTO.BackupStatus?
|
||||
// endregion
|
||||
|
||||
// TODO: move logic to separate domain entity
|
||||
private var resetBackupCardCount = 0
|
||||
|
|
@ -60,29 +76,33 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
value = getInitialState(),
|
||||
)
|
||||
|
||||
private fun getInitialState(): ResetCardScreenState {
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrElse {
|
||||
error("Failed to get user wallet $userWalletId: $it")
|
||||
}
|
||||
val card = userWallet.scanResponse.card
|
||||
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
|
||||
val descriptionText = getResetToFactoryDescription(card, cardTypesResolver)
|
||||
val isTangemWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()
|
||||
val showResetPasswordButton = isTangemWallet && card.backupStatus is CardDTO.BackupStatus.Active
|
||||
init {
|
||||
val cardSpecificInfo = savedStateHandle.get<Bundle>(AppRoute.ResetToFactory.CARD_SPECIFIC_DATA)
|
||||
?.unbundle(AppRoute.ResetToFactory.CardSpecificInfo.serializer())
|
||||
?: error("CardSpecificData must be provided for ResetCardViewModel")
|
||||
|
||||
primaryCardId = cardSpecificInfo.cardId
|
||||
primaryBackupStatus = cardSpecificInfo.backupStatus
|
||||
}
|
||||
|
||||
private fun getInitialState(): ResetCardScreenState {
|
||||
val shouldShowResetPasswordButton = shouldShowResetPasswordButton()
|
||||
val warningsToShow = buildList {
|
||||
add(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS)
|
||||
|
||||
if (showResetPasswordButton) {
|
||||
if (shouldShowResetPasswordButton) {
|
||||
add(ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE)
|
||||
}
|
||||
}
|
||||
|
||||
return ResetCardScreenState(
|
||||
resetButtonEnabled = false,
|
||||
descriptionText = descriptionText,
|
||||
descriptionText = getResetToFactoryDescription(
|
||||
backupStatus = primaryBackupStatus,
|
||||
typesResolver = currentCardTypesResolver,
|
||||
),
|
||||
warningsToShow = warningsToShow,
|
||||
showResetPasswordButton = showResetPasswordButton,
|
||||
showResetPasswordButton = shouldShowResetPasswordButton,
|
||||
acceptCondition1Checked = false,
|
||||
acceptCondition2Checked = false,
|
||||
onAcceptCondition1ToggleClick = ::toggleFirstCondition,
|
||||
|
|
@ -92,6 +112,12 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun shouldShowResetPasswordButton(): Boolean {
|
||||
val isTangemWallet = currentCardTypesResolver.isTangemWallet() || currentCardTypesResolver.isWallet2()
|
||||
|
||||
return isTangemWallet && primaryBackupStatus is CardDTO.BackupStatus.Active
|
||||
}
|
||||
|
||||
private fun toggleFirstCondition(isAccepted: Boolean) {
|
||||
screenState.update { prevState ->
|
||||
val resetButtonEnabled = if (prevState.showResetPasswordButton) {
|
||||
|
|
@ -154,12 +180,9 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
|
||||
private fun makeFullReset() {
|
||||
viewModelScope.launch {
|
||||
val userWallet = getUserWallet()
|
||||
val scanResponse = userWallet.scanResponse
|
||||
|
||||
resetCardUseCase(card = scanResponse.card).onRight {
|
||||
deleteSavedAccessCodesUseCase(scanResponse.card.cardId)
|
||||
val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse {
|
||||
resetCardUseCase(cardId = primaryCardId, params = currentUserCodeParams).onRight {
|
||||
deleteSavedAccessCodesUseCase(cardId = primaryCardId)
|
||||
val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse {
|
||||
Timber.e("Unable to delete user wallet: $it")
|
||||
return@launch
|
||||
}
|
||||
|
|
@ -183,14 +206,15 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
dismissDialog()
|
||||
|
||||
viewModelScope.launch {
|
||||
val userWallet = getUserWallet()
|
||||
resetCardUseCase(
|
||||
cardNumber = resetBackupCardCount + 1,
|
||||
card = userWallet.scanResponse.card,
|
||||
userWalletId = userWalletId,
|
||||
params = currentUserCodeParams,
|
||||
userWalletId = currentUserWalletId,
|
||||
)
|
||||
.onRight {
|
||||
resetBackupCardCount++
|
||||
.onRight { isResetCompleted ->
|
||||
if (isResetCompleted) {
|
||||
resetBackupCardCount++
|
||||
}
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
|
||||
|
|
@ -213,7 +237,7 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun checkRemainingBackupCards() {
|
||||
val backupCardsCount = getUserWallet().scanResponse.getBackupCardsCount()
|
||||
val backupCardsCount = getBackupCardsCount()
|
||||
|
||||
when {
|
||||
backupCardsCount > resetBackupCardCount -> showDialog(ResetCardDialog.ContinueResetDialog)
|
||||
|
|
@ -227,12 +251,6 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getUserWallet(): UserWallet {
|
||||
return getUserWalletUseCase(userWalletId).getOrElse {
|
||||
error("Failed to get user wallet $userWalletId: $it")
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissAndFinishFullReset() {
|
||||
dismissDialog()
|
||||
|
||||
|
|
@ -249,7 +267,7 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
if (isLocked && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||
} else {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -262,10 +280,10 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
screenState.update { it.copy(dialog = null) }
|
||||
}
|
||||
|
||||
private fun ScanResponse.getBackupCardsCount(): Int {
|
||||
if (!cardTypesResolver.isMultiwalletAllowed()) return 0
|
||||
private fun getBackupCardsCount(): Int {
|
||||
if (!currentCardTypesResolver.isMultiwalletAllowed()) return 0
|
||||
|
||||
return when (val status = card.backupStatus) {
|
||||
return when (val status = primaryBackupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
is CardDTO.BackupStatus.NoBackup,
|
||||
|
|
|
|||
|
|
@ -480,7 +480,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val backupValidator = BackupValidator()
|
||||
if (!backupValidator.isValid(CardDTO(result.data))) {
|
||||
if (!backupValidator.isValidBackupStatus(CardDTO(result.data))) {
|
||||
store.dispatchOnMain(BackupAction.ErrorInBackupCard)
|
||||
}
|
||||
if (backupService.currentState == BackupService.State.Finished) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dependencies {
|
|||
|
||||
/* Domain */
|
||||
implementation(projects.domain.qrScanning.models)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.staking.models)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.common.routing.bundle.RouteBundleParams
|
|||
import com.tangem.common.routing.bundle.bundle
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -143,15 +144,26 @@ sealed class AppRoute(val path: String) : Route {
|
|||
@Serializable
|
||||
data object AppSettings : AppRoute(path = "/app_settings")
|
||||
|
||||
/**
|
||||
* Reset to factory route
|
||||
*
|
||||
* @property userWalletId user wallet id
|
||||
* @property cardSpecificInfo info about card that was scanned on CardSettings
|
||||
*/
|
||||
@Serializable
|
||||
data class ResetToFactory(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/reset_to_factory/${userWalletId.stringValue}"), RouteBundleParams {
|
||||
val cardSpecificInfo: CardSpecificInfo,
|
||||
) : AppRoute(path = "/reset_to_factory/${userWalletId.stringValue}/$cardSpecificInfo"), RouteBundleParams {
|
||||
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
|
||||
@Serializable
|
||||
data class CardSpecificInfo(val cardId: String, val backupStatus: CardDTO.BackupStatus?)
|
||||
|
||||
companion object {
|
||||
const val USER_WALLET_ID = "userWalletId"
|
||||
const val CARD_SPECIFIC_DATA = "cardSpecificInfo"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ object BigDecimalFormatter {
|
|||
maximumFractionDigits = cryptoCurrency.decimals
|
||||
minimumFractionDigits = 2
|
||||
isGroupingUsed = true
|
||||
roundingMode = RoundingMode.DOWN
|
||||
roundingMode = RoundingMode.HALF_UP
|
||||
}
|
||||
|
||||
return formatter.format(cryptoAmount).let {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.domain.card
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.card.models.ResetCardError
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
|
|
@ -12,13 +11,18 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
*/
|
||||
interface ResetCardUseCase {
|
||||
|
||||
/** Reset card [card] to factory settings */
|
||||
suspend operator fun invoke(card: CardDTO): Either<ResetCardError, Unit>
|
||||
/** Reset card [cardId] to factory settings */
|
||||
suspend operator fun invoke(cardId: String, params: ResetCardUserCodeParams): Either<ResetCardError, Boolean>
|
||||
|
||||
/** Reset backup card [cardNumber] with expected [UserWalletId] using [card] of reset card */
|
||||
/** Reset backup card [cardNumber] with expected [UserWalletId] using [params] of reset card */
|
||||
suspend operator fun invoke(
|
||||
cardNumber: Int,
|
||||
card: CardDTO,
|
||||
params: ResetCardUserCodeParams,
|
||||
userWalletId: UserWalletId,
|
||||
): Either<ResetCardError, Unit>
|
||||
}
|
||||
): Either<ResetCardError, Boolean>
|
||||
}
|
||||
|
||||
data class ResetCardUserCodeParams(
|
||||
val isAccessCodeSet: Boolean,
|
||||
val isPasscodeSet: Boolean?,
|
||||
)
|
||||
|
|
@ -8,6 +8,8 @@ import java.math.BigDecimal
|
|||
|
||||
sealed interface LegacyAction : Action {
|
||||
|
||||
data class SendEmailSupport(val scanResponse: ScanResponse) : LegacyAction
|
||||
|
||||
data class SendEmailRateCanBeBetter(val scanResponse: ScanResponse) : LegacyAction
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.kotlin.serialization)
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ import com.tangem.common.card.EncryptionMode
|
|||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.operations.attestation.Attestation
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.Date
|
||||
import com.tangem.common.card.FirmwareVersion as SdkFirmwareVersion
|
||||
|
||||
|
|
@ -298,11 +300,19 @@ data class CardDTO(
|
|||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class BackupStatus {
|
||||
|
||||
@Serializable
|
||||
@SerialName("card_linked")
|
||||
data class CardLinked(val cardCount: Int) : BackupStatus()
|
||||
|
||||
@Serializable
|
||||
@SerialName("active")
|
||||
data class Active(val cardCount: Int) : BackupStatus()
|
||||
|
||||
@Serializable
|
||||
@SerialName("no_backup")
|
||||
data object NoBackup : BackupStatus()
|
||||
|
||||
val isActive: Boolean
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ dependencies {
|
|||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
implementation(deps.compose.accompanist.permission)
|
||||
implementation(deps.compose.accompanist.webView)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.material)
|
||||
|
||||
|
|
@ -41,6 +42,9 @@ dependencies {
|
|||
implementation(projects.features.disclaimer.api)
|
||||
implementation(projects.features.pushNotifications.api)
|
||||
|
||||
/** Other dependencies */
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
package com.tangem.features.disclaimer.impl.local
|
||||
|
||||
internal val localTermsOfServices = """
|
||||
<!DOCTYPE html>
|
||||
<!-- saved from url=(0034)https://tangem.com/tangem_tos.html -->
|
||||
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="transparent" content="true">
|
||||
<title>Legal Disclaimer</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--txt-color: rgb(0, 0, 0);
|
||||
--bg-color: rgb(255, 255, 255);
|
||||
}
|
||||
@font-face {
|
||||
font-family: "SF Pro Display";
|
||||
src: url("/fonts/sf-pro/SFProDisplay-Light.eot") format("embedded-opentype"), url("/fonts/sf-pro/SFProDisplay-Light.woff2") format("woff2"), url("/fonts/sf-pro/SFProDisplay-Light.woff") format("woff"), url("/fonts/sf-pro/SFProDisplay-Light.ttf") format("truetype");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "SF Pro Display";
|
||||
src: url("/fonts/sf-pro/SFProDisplay-Regular.eot") format("embedded-opentype"), url("/fonts/sf-pro/SFProDisplay-Regular.woff2") format("woff2"), url("/fonts/sf-pro/SFProDisplay-Regular.woff") format("woff"), url("/fonts/sf-pro/SFProDisplay-Regular.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "SF Pro Display";
|
||||
src: url("/fonts/sf-pro/SFProDisplay-Medium.eot") format("embedded-opentype"), url("/fonts/sf-pro/SFProDisplay-Medium.woff2") format("woff2"), url("/fonts/sf-pro/SFProDisplay-Medium.woff") format("woff"), url("/fonts/sf-pro/SFProDisplay-Medium.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "SF Pro Display";
|
||||
src: url("/fonts/sf-pro/SFProDisplay-Semibold.eot") format("embedded-opentype"), url("/fonts/sf-pro/SFProDisplay-Semibold.woff2") format("woff2"), url("/fonts/sf-pro/SFProDisplay-Semibold.woff") format("woff"), url("/fonts/sf-pro/SFProDisplay-Semibold.ttf") format("truetype");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "SF Pro Display";
|
||||
src: url("/fonts/sf-pro/SFProDisplay-Bold.eot") format("embedded-opentype"), url("/fonts/sf-pro/SFProDisplay-Bold.woff2") format("woff2"), url("/fonts/sf-pro/SFProDisplay-Bold.woff") format("woff"), url("/fonts/sf-pro/SFProDisplay-Bold.ttf") format("truetype");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--txt-color: rgb(255, 255, 255);
|
||||
--bg-color: rgb(0, 0, 0);
|
||||
}
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 1rem;
|
||||
/* background-color: var(--bg-color); */
|
||||
background-color: transparent;
|
||||
color: var(--txt-color);
|
||||
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
margin-bottom: 1rem;
|
||||
letter-spacing: -0.24px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.definition {
|
||||
/* background: #fff; */
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 5px solid #007bff;
|
||||
}
|
||||
.definition h3 {
|
||||
margin-top: 0;
|
||||
color: #007bff;
|
||||
}
|
||||
.definition p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h1 {
|
||||
font-weight: 700;
|
||||
font-size: 30px;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.24px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
p.inner {
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>TANGEM WALLET AND TANGEM MOBILE APPLICATION</h1>
|
||||
<h2>Terms of Service</h2>
|
||||
|
||||
<p>PLEASE READ THESE TERMS OF SERVICE CAREFULLY. BY CLICKING TO ACCEPT, OR BY ACCESSING OR USING OUR SERVICES, YOU AGREE THAT YOU HAVE READ, UNDERSTOOD, AND ACCEPT ALL OF THE TERMS AND CONDITIONS CONTAINED HEREIN. BY PURCHASE OF TANGEM WALLET (CARD) OR BY USING TANGEM WALLET (CARD) OR BY USING TANGEM MOBILE APPLICATION, YOU DEMONSTRATE YOUR AGREEMENT TO THESE TERMS AND CONDITIONS CONTAINED HEREIN.</p>
|
||||
|
||||
<h3>1. DEFINITIONS</h3>
|
||||
<p><strong>“Cardholder”</strong> refers to an individual who owns a Tangem Card that is used to access the Tangem Wallet and Tangem Mobile Application.</p>
|
||||
<p><strong>“Blockchain Asset”</strong> refers to digital assets, including but not limited to cryptocurrencies, that can be managed through the Tangem Wallet.</p>
|
||||
<p><strong>“Blockchain Address”</strong> means a unique identifier that serves as a virtual location of the Blockchain Asset in the blockchain.</p>
|
||||
<p><strong>“Card Transaction”</strong> means transfer of Blockchain Asset from the Blockchain Address associated with the Public Key stored on the Tangem Wallet (Card).</p>
|
||||
<p><strong>“Official Mobile Application”</strong> means an application developed and distributed by Tangem, providing interoperability between Tangem Wallet (Card) and blockchain, and working on NFC-capable smartphones and tablets using Google Android and Apple iOS operation systems.</p>
|
||||
<p><strong>“Private Key”</strong> means a secret cryptographic key which provides full control over a Blockchain Asset.</p>
|
||||
<p><strong>“Tangem”</strong> means Tangem AG, a company incorporated under the laws of Switzerland (CHE-390.112.525), with a registered address at Baarerstrasse 10, CH-6300 Zug.</p>
|
||||
<p><strong>“Public Key”</strong> means a cryptographic key, which provides access to information about a Blockchain Asset, including but not limited to Blockchain Address</p>
|
||||
<p><strong>“Services”</strong> means the purchase and/or use of Tangem Wallet (Card) and the services or any other features, technologies or functionalities linked to the Tangem Wallet (Card) provided or operated by Tangem via the website or Official Mobile Applications.</p>
|
||||
<p><strong>“Tangem Wallet (Card) / Tangem Card / Card”</strong> means physical card which stores Private Key and Public Key and is used as a backup card.</p>
|
||||
|
||||
<h3>2. DISCLAIMER</h3>
|
||||
<p>2.1. Bitcoin and other cryptocurrencies are virtual currencies, digital representations of value that are neither issued by a central bank of any state or public authority attached to a conventional currency, but may be used by any natural or legal persons as a means of exchange and can be transferred, stored or traded electronically.</p>
|
||||
<p>2.2. Blockchain technologies and related services are subject to continuous regulatory changes and scrutiny around the world, including but not limited to anti-money laundering and financial regulations. You acknowledge that certain Services, including their availability, could be impacted by one or more regulatory requirements.</p>
|
||||
<p>2.3. <strong>No advice.</strong> No part of the information herein should be considered to be business, legal, financial or tax advice regarding the Products or Services. You should consult your own legal, financial, tax or other professional advisor regarding the matter. By using the Services, you represent that Tangem is responsible neither for obtaining the information about tax or similar obligations arising in relation to usage of the Services nor for fulfillment of such tax (or similar) obligations.</p>
|
||||
|
||||
<h3> 3. GENERAL PROVISIONS</h3>
|
||||
<p>3.1. These Terms of Service (the “Terms”) govern the use of Tangem Wallet and/or Official Mobile Application provided by Tangem (referred to as "Tangem", "we" or "us" in this document) and the related services or any other features, technologies or functionalities linked to Tangem Wallet (Card). Tangem is a company incorporated under the laws of Switzerland (CHE-390.112.525), with a registered address at Baarerstrasse 10, CH-6300 Zug.</p>
|
||||
<p>3.2. Tangem Cards may be used for storage of Private Key and Public Key to Cardholder’s Blockchain Assets and authentication of Card Transactions with the purpose of “person-to-person” transfer of Blockchain Assets to another blockchain address.</p>
|
||||
<p>3.3. Official Mobile Application is intended for usage only with Tangem Wallet (Card), providing interoperability between the cards and blockchain via NFC interface. Official Mobile Application DOES NOT:</p>
|
||||
<p class="inner">3.3.1. Generate, store, transmit, or have access to private (secret) cryptographic keys to blockchain wallets holding Blockchain assets.</p>
|
||||
<p class="inner">3.3.2. Generate, store, transmit, or have access to secret keys, passwords, passphrases, recovery phrases that can be used to restore or to copy private (secret) keys to blockchain wallets holding Blockchain assets.</p>
|
||||
<p class="inner">3.3.3 Provide exchange, trading, investment services on behalf of Tangem.</p>
|
||||
|
||||
<h3>4. RIGHTS AND OBLIGATIONS</h3>
|
||||
<p>4.1. Cardholder agrees that these Terms are binding.</p>
|
||||
<p>4.2. Cardholder shall be the only person having physical access to Tangem Wallet.</p>
|
||||
<p>4.3. Cardholder acknowledges and agrees that Tangem does not provide backup or recovery of Private Key and Public Key stored by Tangem Wallet (Card).</p>
|
||||
<p>4.4. Tangem does not keep records of Cardholder’s personal data, the amount of Blockchain Asset stored on the Card, the Private Key, or personalized history of Card usage.</p>
|
||||
|
||||
<h3>5. COSTS</h3>
|
||||
<p>5.1. Costs, fees and commission (the “Costs”) may be charged in connection with the use of Card. These Costs are disclosed in Official Mobile Applications to Cardholder.</p>
|
||||
<p>5.2. Amendments to Costs due to changing expenses or market conditions may be made at any time via adjustments to the fee schedules. Such amendments shall be communicated to Cardholder in an appropriate manner. Upon notification and in the event of the objection, Cardholder may cancel the Card with immediate effect.</p>
|
||||
|
||||
<h3>6. CARDHOLDER’S DUTIES OF CARE</h3>
|
||||
<p class="inner">6.1. In particular, Cardholder shall exercise the following duties of care:</p>
|
||||
<p class="inner">6.1.1. Upon receiving the Card, Cardholder should download Official Mobile Applications in order to create the backup and if applicable determine the amount of Blockchain Asset stored through Official Mobile Applications.</p>
|
||||
<p class="inner">6.1.2. Cardholder shall keep the means of access and Tangem Wallet (Card) with care and all Cards separate from each other.</p>
|
||||
<p class="inner">6.1.3. Cardholder must always know where Tangem Wallet (Card) is and regularly ensure that it is still in his/her possession. He/she shall avoid even temporary possession of Tangem Wallet (Card) by any other person.</p>
|
||||
<p class="inner">6.1.4. Cardholder shall treat Tangem Wallet in the same manner as physical money (cash) and keep it safe. If any of the Card is lost, stolen or destroyed, control over the corresponding Blockchain Asset may be permanently lost.</p>
|
||||
<p class="inner">6.1.5. Before using the Card with Official Mobile Applications, Cardholder shall locate Official Mobile Applications in Google Play or Apple app store and install it as instructed in the Card box.</p>
|
||||
<p class="inner">6.1.6. Card shall be used only with Official Mobile Applications and as instructed in the Card box.</p>
|
||||
<p class="inner">6.1.7. Official Mobile Application shall be the only source of information about the Blockchain Address of the Blockchain Asset and corresponding Public Key stored on Tangem Wallet (Card).</p>
|
||||
<p class="inner">6.1.8. Cardholder shall only use Near-Field Devices (the “NFC”) devices that are capable of running Official Mobile Applications. He/she shall avoid leaving Tangem Wallet (Card) in the proximity of the NFC devices of other persons.</p>
|
||||
<p class="inner">6.1.9. Tangem Wallet (Card) shall be used only for physically tapping and holding near Cardholder’s NFC device when Official Mobile Application requests it.</p>
|
||||
<p class="inner">6.1.10. Cardholder shall keep Tangem Wallet (Card) with care and protect Tangem Wallet (Card) from mechanical damage, high temperatures, strong electromagnetic fields, and other harmful factors.</p>
|
||||
<p>6.2. <strong>No retrieval of Private Keys.</strong> Tangem operates non-custodial services, which means that we do not store, nor do we have access to your Blockchain Assets nor your Private Keys. Tangem does not have access to or store passwords, 24-word Recovery Phrase, Private Keys, passphrases, transaction history, PIN, or other credentials associated with your use of the Services. You are solely responsible for remembering, storing, and keeping your credentials in a secure location, away from prying eyes. Any third party with knowledge of one or more of your 24-word Recovery Phrase can gain control of the Private Keys associated with your Tangem Wallet (Card) or of the 24-word Recovery Phrase, and therefore steal your Blockchain Assets, without any possibility for you or Tangem to retrieve them.</p>
|
||||
|
||||
<h3>7. RIGHTS AND RESPONSIBILITIES OF CARDHOLDER</h3>
|
||||
<p>7.1. Cardholder is liable for all liabilities arising from the use of Tangem Wallet (Card) and/or Tangem Official Mobile Application. Any disputes in relation to discrepancies and complaints about goods or services and any resulting claims must be settled directly by Cardholder with the respective Reseller.</p>
|
||||
<p>7.2. As a matter of principle, Cardholder is liable for any risks resulting from the misuse of Tangem Wallet (Card) and/or Official Mobile Application. In any case, Cardholder is solely liable for all transactions authorized using a means of access.</p>
|
||||
<p>7.3. Any loss or damage resulting from the forwarding of Tangem Wallet (Card) and/or means of access shall be borne by Cardholder.</p>
|
||||
<p>7.4. Loss or damage incurred by Cardholder in connection with the possession or use of Tangem Wallet (Card) and/or Official Mobile Application shall be borne solely by Cardholder. Tangem assumes no liability if Tangem Wallet (Card) and/or Official Mobile Application cannot be used due to a technical defect or because it has been canceled, blocked or the spending limit has been adjusted.</p>
|
||||
<p>7.5. Cardholder is only permitted to use Tangem Wallet (Card) and Official Mobile Application for his personal, non-commercial use. Cardholder is not allowed to resell Tangem Wallet (Card).</p>
|
||||
<p>7.6. Cardholder is solely responsible to determinate what, if any, taxes apply to Card Transactions. Tangem or contributors to Official Mobile Applications are NOT responsible for determining the taxes that apply to Card Transactions.</p>
|
||||
<p>7.7. Before Cardholder engages in transactions using an electronic system, Cardholder should carefully review the rules and regulations of the exchanges offering the system and/or listing the instruments Cardholder intends to trade. Online trading has inherent risk due to system response and access times that may vary due to market conditions, system performance, and other factors. Cardholder should understand, fully accept and take on these and additional risks before trading.</p>
|
||||
<p>7.8. There is considerable exposure to risk in the Blockchain Asset exchange transaction. Any transaction involving the Blockchain Asset involves risks including, but not limited to, the potential for changing economic conditions that may substantially affect the price or liquidity of the Blockchain Asset. Investments in the Blockchain Asset exchange speculation may also be susceptible to sharp rises and falls as the relevant market values fluctuate. It is for this reason that when speculating in such markets it is advisable to use only risk capital.</p>
|
||||
<p>7.9. Before initiating any transactions through third-party resources via widgets or links within the application, Cardholder is expressly advised to meticulously review and comprehend the terms, rules, and regulations governing such resources. It is imperative for Cardholder to be cognizant of the inherent risks associated with online trading, including variations in system response times, access delays influenced by market conditions, system performance, and other pertinent factors.</p>
|
||||
<p>7.10. Cardholder unequivocally assumes sole responsibility for all actions undertaken, encompassing but not limited to swap transactions, on-ramp, and off-ramp activities, when transitioning to third-party resources through widgets or links within the application. This responsibility extends to compliance with the terms and conditions of the relevant third-party resources and adherence to applicable laws and regulations.</p>
|
||||
<p>7.11. Cardholder acknowledges and accepts that the use of third-party resources involves inherent risks, and Tangem shall bear no liability for the consequences arising from Cardholder's independent actions on these external platforms.</p>
|
||||
<p>7.12. Cardholder is responsible for implementing adequate security measures and precautions when interacting with third-party resources to safeguard personal information, financial assets, and to mitigate potential risks associated with such engagements.</p>
|
||||
<p>7.13. Cardholder agrees to indemnify and hold Tangem, its affiliates, and service providers harmless from any claims, losses, or damages incurred as a result of their actions on third-party platforms, as outlined in the Terms of Services.</p>
|
||||
<p>7.14. Tangem explicitly disclaims any affiliation, endorsement, or responsibility for the content, policies, or transactions on third-party resources, and Cardholder interactions with such resources are entirely at their own risk.</p>
|
||||
|
||||
<h3>8. THIRD-PARTY SERVICES</h3>
|
||||
<p>8.1. We may incorporate, reference and/or provide access to Third Party Services. For instance, buy, sell and crypto to crypto exchange (“swap”) services are Third Party Services. You agree that your use of Third-Party Services is subject to separate terms and conditions between you and the third-party identified in Tangem.</p>
|
||||
<p>8.2. Tangem is not responsible for the content, accuracy, security, availability, any performance, or failure to perform of the Third-Party Services or any issue in relation with the use of Third-Party Services. Tangem does not provide any guarantees that access to Third-Party Services will not be interrupted or that there will be no delays, failures, errors, omissions, corruption or loss of transmitted information, data or funds, and Tangem shall not be liable for any such Third-Party Services. You agree to use the Third-Party Services at your own risk. It is your responsibility to review the third party’s terms and policies before using a Third-Party Service. Third-Party Services may not be available in all languages and may not be appropriate or available for use in any particular location. To the extent you choose to use such Third-Party Services, you are solely responsible for compliance with any applicable laws in relation to such use. In addition, Tangem reserves the right to block access to these Third-Party Services through Tangem Live in particular, but not exclusively, in the event of non-compliance with the applicable regulations by the Third-Party partner. We retain the exclusive right to suspend, remove, or cancel the availability of any such Third-Party Service for any reason and without prior notice.</p>
|
||||
|
||||
<h3>9. RESPONSIBILITIES AND LIABILITIES OF TANGEM</h3>
|
||||
<p>9.1. Tangem does not warrant or make any representations regarding the use, the inability to use or operate, or the results of the use or operation of Tangem Wallet (Card) and/or Official Mobile Application.</p>
|
||||
<p>9.2. Tangem does not keep any records of Cardholder information, the amount of Blockchain Asset stored on Tangem Wallet (Card), the Private Key, or personalized history of cards usage.</p>
|
||||
<p>9.3. Tangem does not provide any backup or recovery of Private Key and Public Key stored on Tangem Wallet (Card).</p>
|
||||
<p>9.4. Tangem shall not be held liable for any failure to be able to use Tangem Wallet (Card) and/or Official Mobile Application, for any reason whatsoever, nor will Tangem be held liable for the loss of the Blockchain Asset resulting from a malfunction or inoperability of the blockchain network hosting Blockchain Asset, as well as the inaccessibility of its public servers and services.</p>
|
||||
<p>9.5. Tangem does not guarantee that the operation of Tangem Wallet (Card) and/or Official Mobile Application will be secure, accurate, complete, uninterrupted, without error or free of viruses, worms, other harmful components or other program limitations. Tangem may, at its sole discretion and without obligation to do so, correct, modify, amend, enhance, improve and make any other changes to Tangem Wallet and/or Official Mobile Application, change, update or suspend the Services, temporarily or indefinitely, so as to carry out works including, but not limited to: firmware and software updates, maintenance operations, amendments to the servers, bug fixes, etc. We will make reasonable efforts to give you prior notice of any significant disruption of the Services. Tangem does not guarantee the correct functioning of the Services in the event of the installation or use of programs or applications that do not conform to Service specifications and technical standards.</p>
|
||||
<p>9.6. Tangem shall not be held liable for the loss of profits, income, value or any indirect, extraordinary, consequential, exemplary or punitive damages.</p>
|
||||
<p>9.7. Tangem shall not be held liable for loss or breakdown of Tangem Wallet (Card).</p>
|
||||
<p>9.8. Tangem shall not be held liable for any loss of Blockchain Asset in the event of loss or total breakdown of Tangem Wallet (Card).</p>
|
||||
|
||||
<h3>10. GUARANTEES</h3>
|
||||
<p>10.1. Under the condition that Cardholder exercises the duties of care as stated in Clause 5, Tangem guarantees that Tangem Wallet (Card) will function properly and without restriction for a period of 2 (two) years. In the event of a breakdown of Tangem Wallet (Card) without it being the fault of Cardholder due to reasons mentioned in Clause 5 of these Terms, Tangem will replace Tangem Wallet with a new one. Cardholder shall inform Tangem on the event of a breakdown by sending an e-mail to support@tangem.com. If failed to resolve with support team of Tangem, Cardholder shall wait for instructions on safe shipping of Tangem Wallet (Card).</p>
|
||||
<p>10.2. Tangem guarantees that Tangem Wallet (Card) prevents duplication of the Private Key and that Cardholder has exclusive control over the Blockchain Asset unless the contrary is imposed by specific blockchain network rules, e.g. two or more private keys can be used to control the same Blockchain Asset.</p>
|
||||
|
||||
<h3>11. LIMITATION OF LIABILITY</h3>
|
||||
<p>11.1. Tangem Wallet (Card), including without limitation any content, data and information related thereto, is provided on an “as is” basis and “as available” basis, without any warranties of any kind, express or implied warranties of use, merchantability or suitability for a certain purpose or use, including without limitation, the quality of products and services provided by users, third-party services, and/or exchanges (except for the guarantees set forth in Section 9).</p>
|
||||
<p>11.2. Official Mobile Application is provided on an “as is” basis and “as available” basis without any warranties of any kind regarding Official Mobile Application and/or any content, data, materials and/or services provided on Official Mobile Application.</p>
|
||||
<p>11.3. Tangem and its affiliates, including any of their officers, directors, shareholders, employees, sub-contractors, agents, parent companies, subsidiaries and other affiliates (collectively, the “Tangem Affiliates”), jointly and severally, disclaim and make no representations or warranties as to the usability, accuracy, quality, availability, reliability, suitability, completeness, truthfulness, usefulness or effectiveness of any content, data, results or other information obtained or generated by Tangem and/or any user related to you or any other user of Tangem Wallet (Card), and Official Mobile Applications.</p>
|
||||
<p>11.4. In no event shall Tangem and/or any of Tangem Affiliates be liable for any damages whatsoever, including direct, indirect, extraordinary, incidental or consequential damages of any kind, but not limited to, resulting from or arising out of the use of Tangem Wallet (Card) and/or Official Mobile Applications or inability to use Tangem Wallet (Card) and/or Official Mobile Applications, failure of Tangem Wallet (Card) and/or Official Mobile Applications to perform as represented or expected, loss of goodwill or profits, or loss of data arising out of or in any way connected with the use of Tangem Wallet (Card) and/or Official Mobile Applications. In no event shall Tangem and/or any of Tangem Affiliates be liable for the performance or failure of Tangem Wallet (Card) and/ or Official Mobile Applications to perform under these Terms of Use and any other act or omission by Tangem by any cause whatsoever including without limitation damages arising from the conduct of any users, third party services and/or exchanges. In no way Tangem or contributors to Official Mobile Application are responsible for the actions, decisions, or other behavior taken or not taken by Cardholder in reliance upon Tangem Official Mobile Application.</p>
|
||||
<p>11.5. You hereby acknowledge and agree that these limitations of liability are agreed allocations of risk constituting in part the consideration for using Tangem Wallet (Card) and Official Mobile Applications and such limitations will apply notwithstanding the failure of essential purpose of any limited remedy, and even if Tangem and/or any Tangem Affiliates has been advised of the possibility of such liabilities and/or damages.</p>
|
||||
<p>11.6. Tangem will not be responsible for any losses, damages or claims arising from events falling within the scope of the following five categories:</p>
|
||||
<p class="inner">11.6.1. Mistakes made by Cardholder, e.g., forgotten passwords, payments sent to wrong addresses, and accidental deletion of blockchain wallets on Tangem Wallet (Card).</p>
|
||||
<p class="inner">11.6.2. Problems of Official Mobile Application and/or any blockchain- or cryptocurrency- related software or service, e.g., corrupted files, incorrectly constructed transactions, unsafe cryptographic libraries, malware.</p>
|
||||
<p class="inner">11.6.3. Technical failures in the hardware of Cardholder, including cards, of any blockchain- or cryptocurrency- related software or service, e.g., data loss due to a faulty or damaged storage device.</p>
|
||||
<p class="inner">11.6.4. Security problems experienced by Cardholder, e.g., unauthorized access to Cardholders' wallets and/or accounts.</p>
|
||||
<p class="inner">11.6.5. Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy of service providers, information security attacks on service providers, and fraud conducted by third parties.</p>
|
||||
|
||||
<h3>12. GOVERNING LAW AND DISPUTE RESOLUTION</h3>
|
||||
<p>12.1. Unless otherwise required by a mandatory law of a member state of the European Union or any other jurisdiction these Terms of Service and any separate agreements whereby we provide you Services shall be governed by the laws of Switzerland without regard to its conflict of laws principles.</p>
|
||||
<p>12.2. You can submit a claim in written form regarding the operation of the Services to us via email at store@tangem.com. You may also reach us in writing at the following address: Tangem AG, Baarerstrasse 10, Zug, CH-6300 Switzerland. In case of failure to resolve disputes and disagreements by way of negotiations the settlement shall be in accordance with claim procedure. Claims shall be reviewed within 30 calendar days.</p>
|
||||
<p>12.3. Subject to compulsory legal provisions, any use of the Services and all legal disputes arising out of or in connection therewith shall be submitted to the exclusive jurisdiction of the courts of the Canton of Zug.</p>
|
||||
|
||||
<h3>13. COMMUNICATION</h3>
|
||||
<p>13.1. In the event when under the Terms Tangem provides the User with any information that relates to the Services provided hereunder, this information may be given to the Client through the Website without sending said information directly to the User’s address and / or using other secure means.</p>
|
||||
<p>13.2. Tangem shall respond to requests from the User promptly and within 7 calendar days following the date of receipt of the request. The response time may in some cases may exceed 7 calendar days.</p>
|
||||
|
||||
<h3>14. MISCELLANEOUS</h3>
|
||||
<p>14.1. <strong>Entire agreement.</strong> These Terms and any policies or operating rules posted by us on the Website or in respect to the Services constitutes the entire agreement and understanding between you and us and govern your use of the Services, superseding any prior or contemporaneous agreements, communications and proposals, whether oral or written, between you and us (including, but not limited to, any prior versions of the Terms).</p>
|
||||
<p>14.2. <strong>Severability.</strong> In the event that any provision of these Terms is determined to be unlawful, void or unenforceable, such provision will nonetheless be enforceable to the fullest extent permitted by applicable law, and the unenforceable portion will be deemed to be severed from these Terms of Service, such determination will not affect the validity and enforceability of any other remaining provisions.</p>
|
||||
<p>14.3. <strong>Assignment.</strong> You may not assign your rights or obligations under these Terms in whole or in part to any third party. You acknowledge and agree that Tangem may assign its rights and obligations under these Terms, including rights and obligations concerning insurance, and, in such context, share or transfer information provided by you while using the Services to a third party.</p>
|
||||
<p>14.4. <strong>No waiver.</strong> The failure of us to exercise or enforce any right or provision of these Terms will not constitute a waiver of such right or provision.</p>
|
||||
<p>14.5. Any ambiguities in the interpretation of these Terms will not be construed against the drafting party.</p>
|
||||
<p>14.6. <strong>Errors, Inaccuracies, And Omissions.</strong> Occasionally there may be information in the Services that contains typographical errors, inaccuracies or omissions that may relate to product descriptions, pricing, promotions, offers, product shipping charges, transit times and availability. We reserve the right to correct any errors, inaccuracies or omissions, and to change or update information or cancel orders if any information in the Services or on any related website is inaccurate at any time without prior notice (including after you have submitted your order).</p>
|
||||
<p>14.7. Terms concerning Recovery Phrase apply to Tangem Wallet (Card) supporting this feature.</p>
|
||||
<p>14.8. We undertake no obligation to update, amend or clarify information in the Services, including without limitation, pricing information, except as required by law. No specified update or refresh date applied in the Services or on any related website, should be taken to indicate that all information in the Services has been modified or updated.</p>
|
||||
<p>14.9. These Terms may be drawn up in different languages. In case of any inconsistency the English version of the Terms shall prevail.</p>
|
||||
|
||||
<p>Last amended on: March 1<sup>st</sup>, 2024</p>
|
||||
|
||||
|
||||
</body></html>
|
||||
""".trimIndent()
|
||||
|
|
@ -6,8 +6,11 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.settings.NeverRequestPermissionUseCase
|
||||
import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase
|
||||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||
import com.tangem.features.disclaimer.impl.entity.DisclaimerUM
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -18,6 +21,8 @@ internal class DisclaimerModel @Inject constructor(
|
|||
private val cardRepository: CardRepository,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
|
||||
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -38,6 +43,8 @@ internal class DisclaimerModel @Inject constructor(
|
|||
if (shouldAskPushPermission) {
|
||||
router.push(AppRoute.PushNotification)
|
||||
} else {
|
||||
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
|
||||
neverRequestPermissionUseCase(PUSH_PERMISSION)
|
||||
router.push(AppRoute.Home)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,28 @@ package com.tangem.features.disclaimer.impl.ui
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.res.Configuration
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebView
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.google.accompanist.web.WebView
|
||||
import com.google.accompanist.web.rememberWebViewState
|
||||
import com.google.accompanist.web.rememberWebViewStateWithHTMLData
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
|
|
@ -35,6 +37,7 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
import com.tangem.features.disclaimer.impl.R
|
||||
import com.tangem.features.disclaimer.impl.entity.DisclaimerUM
|
||||
import com.tangem.features.disclaimer.impl.entity.DummyDisclaimer
|
||||
import com.tangem.features.disclaimer.impl.local.localTermsOfServices
|
||||
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
|
||||
|
||||
@Composable
|
||||
|
|
@ -63,6 +66,7 @@ internal fun DisclaimerScreen(state: DisclaimerUM) {
|
|||
iconRes = R.drawable.ic_back_24,
|
||||
onIconClicked = state.popBack,
|
||||
).takeIf { state.isTosAccepted },
|
||||
titleAlignment = Alignment.CenterHorizontally,
|
||||
textColor = textColor,
|
||||
iconTint = iconColor,
|
||||
)
|
||||
|
|
@ -81,52 +85,51 @@ internal fun DisclaimerScreen(state: DisclaimerUM) {
|
|||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
private fun DisclaimerContent(url: String, isTosAccepted: Boolean) {
|
||||
val progressState = remember { mutableStateOf(ProgressState.Loading) }
|
||||
val webClient = remember { DisclaimerWebViewClient(progressState) }
|
||||
val transparent = Color.Transparent
|
||||
val backgroundColor = if (isTosAccepted) TangemTheme.colors.background.primary else TangemColorPalette.Dark6
|
||||
Box(
|
||||
modifier = Modifier,
|
||||
) {
|
||||
AndroidView(
|
||||
factory = {
|
||||
WebView(it).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
)
|
||||
setBackgroundColor(transparent.toArgb())
|
||||
settings.allowFileAccess = false
|
||||
// to inject css style to display only in dark theme
|
||||
settings.javaScriptEnabled = !isTosAccepted
|
||||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
webViewClient = webClient
|
||||
|
||||
clearHistory()
|
||||
clearFormData()
|
||||
clearCache(true)
|
||||
val webViewStateUrl = rememberWebViewState(url)
|
||||
val webViewStateData =
|
||||
rememberWebViewStateWithHTMLData(data = localTermsOfServices, mimeType = "text/html", encoding = "UTF-8")
|
||||
|
||||
loadUrl(url)
|
||||
}
|
||||
val webViewState by remember {
|
||||
derivedStateOf {
|
||||
if (webViewStateUrl.errorsForCurrentRequest.isNotEmpty()) {
|
||||
webViewStateData
|
||||
} else {
|
||||
webViewStateUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box {
|
||||
WebView(
|
||||
state = webViewState,
|
||||
captureBackPresses = false,
|
||||
onCreated = {
|
||||
it.settings.javaScriptEnabled = !isTosAccepted
|
||||
it.setBackgroundColor(backgroundColor.toArgb())
|
||||
},
|
||||
client = remember { DisclaimerWebViewClient() },
|
||||
)
|
||||
|
||||
when (progressState.value) {
|
||||
ProgressState.Loading -> {
|
||||
Box(
|
||||
AnimatedVisibility(
|
||||
visible = webViewState.isLoading,
|
||||
label = "Loading state change animation",
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor),
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor),
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
color = TangemTheme.colors.icon.informative,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.padding(TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
.align(Alignment.Center)
|
||||
.padding(TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.disclaimer.impl.ui
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.webkit.*
|
||||
import androidx.compose.runtime.MutableState
|
||||
import android.webkit.WebView
|
||||
import com.google.accompanist.web.AccompanistWebViewClient
|
||||
|
||||
internal enum class ProgressState {
|
||||
Loading,
|
||||
|
|
@ -26,57 +26,20 @@ private fun WebView.injectCSS() {
|
|||
evaluateJavascript(code, null)
|
||||
}
|
||||
|
||||
internal class DisclaimerWebViewClient(private val progressState: MutableState<ProgressState>) : WebViewClient() {
|
||||
|
||||
private var loadingUrl: String? = null
|
||||
private var loadedUrl: String? = null
|
||||
|
||||
fun reset() {
|
||||
loadingUrl = null
|
||||
loadedUrl = null
|
||||
progressState.value = ProgressState.Loading
|
||||
}
|
||||
internal class DisclaimerWebViewClient : AccompanistWebViewClient() {
|
||||
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
view?.injectCSS()
|
||||
super.onPageStarted(view, url, favicon)
|
||||
}
|
||||
|
||||
if (loadingUrl != url && progressState.value != ProgressState.Error) progressState.value = ProgressState.Loading
|
||||
loadingUrl = url
|
||||
override fun onPageCommitVisible(view: WebView?, url: String?) {
|
||||
view?.injectCSS()
|
||||
super.onPageCommitVisible(view, url)
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
view?.injectCSS()
|
||||
super.onPageFinished(view, url)
|
||||
|
||||
if (loadedUrl != url && progressState.value != ProgressState.Error) progressState.value = ProgressState.Done
|
||||
loadedUrl = url
|
||||
}
|
||||
|
||||
override fun onReceivedError(view: WebView?, resourceRequest: WebResourceRequest?, error: WebResourceError?) {
|
||||
view?.injectCSS()
|
||||
super.onReceivedError(view, resourceRequest, error)
|
||||
error?.let { progressState.value = ProgressState.Error }
|
||||
}
|
||||
|
||||
override fun onReceivedHttpError(
|
||||
view: WebView?,
|
||||
resourceRequest: WebResourceRequest?,
|
||||
errorResponse: WebResourceResponse?,
|
||||
) {
|
||||
view?.injectCSS()
|
||||
super.onReceivedHttpError(view, resourceRequest, errorResponse)
|
||||
|
||||
if (resourceRequest != null && errorResponse != null) {
|
||||
val isDifferentUrl = resourceRequest.url?.toString() != loadingUrl
|
||||
val isSuccessCode = errorResponse.statusCode < RESPONSE_USER_ERROR_STATUS_CODE
|
||||
val isNotDone = progressState.value != ProgressState.Done
|
||||
if (isDifferentUrl || isSuccessCode || isNotDone) return
|
||||
progressState.value = ProgressState.Error
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RESPONSE_USER_ERROR_STATUS_CODE = 400
|
||||
}
|
||||
}
|
||||
|
|
@ -7,10 +7,14 @@ import javax.inject.Inject
|
|||
|
||||
class BackupValidator @Inject constructor() {
|
||||
|
||||
fun isValid(cardDTO: CardDTO): Boolean {
|
||||
fun isValidFull(cardDTO: CardDTO): Boolean {
|
||||
return validateBackupStatus(cardDTO) && validateCurves(cardDTO)
|
||||
}
|
||||
|
||||
fun isValidBackupStatus(cardDTO: CardDTO): Boolean {
|
||||
return validateBackupStatus(cardDTO)
|
||||
}
|
||||
|
||||
private fun validateCurves(cardDTO: CardDTO): Boolean {
|
||||
val config = CardConfig.createConfig(cardDTO)
|
||||
// / Since the curve `bls12381_G2_AUG` was added later into first generation of wallets,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
buildList {
|
||||
addSwapPromoNotification(shouldShowPromo, promoBanner, clickIntents)
|
||||
|
||||
addCriticalNotifications(userWallet)
|
||||
addCriticalNotifications(userWallet, clickIntents)
|
||||
|
||||
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
|
||||
|
||||
|
|
@ -87,11 +87,14 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addCriticalNotifications(userWallet: UserWallet) {
|
||||
private fun MutableList<WalletNotification>.addCriticalNotifications(
|
||||
userWallet: UserWallet,
|
||||
clickIntents: WalletClickIntents,
|
||||
) {
|
||||
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
|
||||
addIf(
|
||||
element = WalletNotification.Critical.BackupError,
|
||||
condition = !backupValidator.isValid(userWallet.scanResponse.card) || userWallet.hasBackupError,
|
||||
element = WalletNotification.Critical.BackupError { clickIntents.onSupportClick() },
|
||||
condition = !backupValidator.isValidBackupStatus(userWallet.scanResponse.card) || userWallet.hasBackupError,
|
||||
)
|
||||
|
||||
addIf(
|
||||
|
|
|
|||
|
|
@ -19,11 +19,16 @@ import org.joda.time.DateTime
|
|||
@Immutable
|
||||
sealed class WalletNotification(val config: NotificationConfig) {
|
||||
|
||||
sealed class Critical(title: TextReference, subtitle: TextReference) : WalletNotification(
|
||||
sealed class Critical(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonsState = buttonsState,
|
||||
),
|
||||
) {
|
||||
|
||||
|
|
@ -37,9 +42,13 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
subtitle = resourceReference(id = R.string.warning_failed_to_verify_card_message),
|
||||
)
|
||||
|
||||
data object BackupError : Critical(
|
||||
data class BackupError(val onSupportClick: () -> Unit) : Critical(
|
||||
title = resourceReference(R.string.warning_backup_errors_title),
|
||||
subtitle = resourceReference(R.string.warning_backup_errors_message),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(id = R.string.details_row_title_contact_to_support),
|
||||
onClick = onSupportClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ internal interface WalletWarningsClickIntents {
|
|||
fun onTravalaPromoClick(link: String?)
|
||||
|
||||
fun onCloseTravalaPromoClick()
|
||||
|
||||
fun onSupportClick()
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -262,6 +264,15 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onSupportClick() {
|
||||
reduxStateHolder.dispatch(
|
||||
LegacyAction.SendEmailSupport(
|
||||
scanResponse = getSelectedUserWallet()?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSelectedUserWallet(): UserWallet? {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
return getUserWalletUseCase(userWalletId).getOrElse {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ markdown = "0.7.2"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-702"
|
||||
tangemBlockchainSdk = "develop-703"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-375"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
versionName=5.13.0
|
||||
versionName=5.14.0
|
||||
Loading…
Add table
Add a link
Reference in a new issue