Updated on 2026-08-14
This commit is contained in:
parent
1bf78a1f1a
commit
f25889d1a0
30 changed files with 149 additions and 275 deletions
|
|
@ -27,20 +27,12 @@ configurations.all {
|
|||
|
||||
resolutionStrategy {
|
||||
dependencySubstitution {
|
||||
substitute(module("com.facebook.react:react-native"))
|
||||
.using(module("com.facebook.react:react-android:0.72.4"))
|
||||
|
||||
substitute(module("com.facebook.react:hermes-engine"))
|
||||
.using(module("com.facebook.react:hermes-android:0.72.4"))
|
||||
|
||||
substitute(module("org.bouncycastle:bcprov-jdk15on"))
|
||||
.using(module("org.bouncycastle:bcprov-jdk18on:1.73"))
|
||||
}
|
||||
|
||||
force(
|
||||
"org.bouncycastle:bcpkix-jdk15on:1.70",
|
||||
"com.facebook.react:react-android:0.72.4",
|
||||
"com.facebook.react:hermes-android:0.72.4",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -224,10 +216,6 @@ dependencies {
|
|||
implementation(deps.walletConnectCore)
|
||||
implementation(deps.walletConnectWeb3)
|
||||
implementation(deps.prettyLogger)
|
||||
implementation("com.facebook.react:react-android:0.72.4")
|
||||
implementation(deps.sprClient) {
|
||||
exclude(group = "com.github.stephenc.jcip")
|
||||
}
|
||||
|
||||
/** Testing libraries */
|
||||
testImplementation(deps.test.coroutine)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
|
|
@ -49,7 +48,7 @@ internal class LockUserWalletsTimer(
|
|||
start()
|
||||
|
||||
if (shouldOpenWelcomeScreenOnResume) {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false)
|
||||
}
|
||||
}
|
||||
|
|
@ -127,7 +126,7 @@ internal class LockUserWalletsTimer(
|
|||
if (wasApplicationStopped) {
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
|
||||
} else {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ import com.tangem.tap.common.analytics.AnalyticsFactory
|
|||
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
|
||||
import com.tangem.tap.common.analytics.handlers.amplitude.AmplitudeAnalyticsHandler
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
|
||||
import com.tangem.tap.common.chat.ChatManager
|
||||
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
|
||||
import com.tangem.tap.common.feedback.LegacyFeedbackManager
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
|
|
@ -316,7 +315,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
private fun initWithConfigDependency(config: Config) {
|
||||
initAnalytics(this, config)
|
||||
initFeedbackManager(this, foregroundActivityObserver, store)
|
||||
initFeedbackManager(this, store)
|
||||
}
|
||||
|
||||
private fun initAnalytics(application: Application, config: Config) {
|
||||
|
|
@ -337,11 +336,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
// ExceptionHandler.append(blockchainExceptionHandler) TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
private fun initFeedbackManager(
|
||||
context: Context,
|
||||
foregroundActivityObserver: ForegroundActivityObserver,
|
||||
store: Store<AppState>,
|
||||
) {
|
||||
private fun initFeedbackManager(context: Context, store: Store<AppState>) {
|
||||
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo {
|
||||
return AdditionalFeedbackInfo().apply {
|
||||
appVersion = try {
|
||||
|
|
@ -382,7 +377,6 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
val feedbackManager = LegacyFeedbackManager(
|
||||
infoHolder = additionalFeedbackInfo,
|
||||
logCollector = tangemLogCollector,
|
||||
chatManager = ChatManager(foregroundActivityObserver),
|
||||
feedbackManagerFeatureToggles = feedbackManagerFeatureToggles,
|
||||
getFeedbackEmailUseCase = getFeedbackEmailUseCase,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.common.chat
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.datasource.config.models.ChatConfig
|
||||
import com.tangem.datasource.config.models.SprinklrConfig
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.tap.common.chat.opener.ChatOpener
|
||||
import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener
|
||||
import java.io.File
|
||||
|
||||
class ChatManager(private val foregroundActivityObserver: ForegroundActivityObserver) {
|
||||
private val openers = mutableMapOf<ChatConfig, ChatOpener>()
|
||||
|
||||
fun open(config: ChatConfig, createLogsFile: (Context) -> File?, createFeedbackFile: (Context) -> File?) {
|
||||
val opener = openers.getOrPut(config) {
|
||||
when (config) {
|
||||
is SprinklrConfig -> SprinklrChatOpener(config, foregroundActivityObserver)
|
||||
}
|
||||
}
|
||||
|
||||
opener.open(createFeedbackFile, createLogsFile)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.tap.common.chat.opener
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
|
||||
internal interface ChatOpener {
|
||||
fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?)
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package com.tangem.tap.common.chat.opener.implementation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.provider.Settings
|
||||
import com.spr.messengerclient.config.SPRMessenger
|
||||
import com.spr.messengerclient.config.bean.SPRMessengerConfig
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.datasource.config.models.SprinklrConfig
|
||||
import com.tangem.tap.ForegroundActivityObserver
|
||||
import com.tangem.tap.common.chat.opener.ChatOpener
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
internal class SprinklrChatOpener(
|
||||
private val config: SprinklrConfig,
|
||||
private val foregroundActivityObserver: ForegroundActivityObserver,
|
||||
) : ChatOpener {
|
||||
|
||||
override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) {
|
||||
val messenger = SPRMessenger.shared()
|
||||
|
||||
if (messenger.config == null) {
|
||||
initSprConfig(messenger)
|
||||
}
|
||||
|
||||
messenger.startApplication()
|
||||
}
|
||||
|
||||
private fun initSprConfig(messenger: SPRMessenger) {
|
||||
val application = foregroundActivityObserver.foregroundActivity?.application.guard {
|
||||
Timber.e("The SPR chat cannot be opened because there are no activities in foreground")
|
||||
return
|
||||
}
|
||||
|
||||
messenger.takeOff(application, createSprConfig(application, config))
|
||||
}
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
private fun createSprConfig(application: Application, config: SprinklrConfig): SPRMessengerConfig {
|
||||
return SPRMessengerConfig().apply {
|
||||
appId = config.appId
|
||||
appKey = CHAT_APP_KEY
|
||||
deviceId = Settings.Secure.getString(application.contentResolver, Settings.Secure.ANDROID_ID)
|
||||
environment = config.environment
|
||||
skin = CHAT_SKIN
|
||||
locale = Locale.getDefault().language
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CHAT_APP_KEY = "com.sprinklr.messenger.release"
|
||||
const val CHAT_SKIN = "MODERN"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,11 @@ package com.tangem.tap.common.feedback
|
|||
|
||||
import android.content.Context
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.datasource.config.models.ChatConfig
|
||||
import com.tangem.domain.common.TapWorkarounds
|
||||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.feedback.GetFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.chat.ChatManager
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.sendEmail
|
||||
import com.tangem.tap.common.log.TangemLogCollector
|
||||
|
|
@ -29,12 +27,10 @@ import java.io.StringWriter
|
|||
class LegacyFeedbackManager(
|
||||
val infoHolder: AdditionalFeedbackInfo,
|
||||
private val logCollector: TangemLogCollector,
|
||||
private val chatManager: ChatManager,
|
||||
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
|
||||
private val getFeedbackEmailUseCase: GetFeedbackEmailUseCase,
|
||||
) {
|
||||
|
||||
private var sessionFeedbackFile: File? = null
|
||||
private var sessionLogsFile: File? = null
|
||||
|
||||
fun sendEmail(feedbackData: FeedbackData, scanResponse: ScanResponse?) {
|
||||
|
|
@ -97,43 +93,6 @@ class LegacyFeedbackManager(
|
|||
}
|
||||
}
|
||||
|
||||
fun openChat(config: ChatConfig, feedbackData: FeedbackData) {
|
||||
chatManager.open(
|
||||
config = config,
|
||||
createLogsFile = ::getLogFile,
|
||||
createFeedbackFile = { context -> getFeedbackFile(context, feedbackData) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun getFeedbackFile(context: Context, feedbackData: FeedbackData): File? {
|
||||
return try {
|
||||
if (sessionFeedbackFile != null) {
|
||||
return sessionFeedbackFile
|
||||
}
|
||||
val file = File(context.filesDir, FEEDBACK_FILE)
|
||||
file.delete()
|
||||
file.createNewFile()
|
||||
|
||||
val feedback = feedbackData.run {
|
||||
prepare(infoHolder)
|
||||
joinTogether(context, infoHolder)
|
||||
}
|
||||
val fileWriter = FileWriter(file)
|
||||
fileWriter.write(feedback)
|
||||
fileWriter.close()
|
||||
|
||||
if (file.exists()) {
|
||||
sessionFeedbackFile = file
|
||||
sessionFeedbackFile
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Can't create the logs file")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLogFile(context: Context): File? {
|
||||
return try {
|
||||
if (sessionLogsFile != null) {
|
||||
|
|
@ -172,7 +131,6 @@ class LegacyFeedbackManager(
|
|||
private companion object {
|
||||
const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com"
|
||||
const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com"
|
||||
const val FEEDBACK_FILE = "feedback.txt"
|
||||
const val LOGS_FILE = "logs.txt"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.common.redux.global
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.config.models.ChatConfig
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
|
|
@ -79,7 +78,6 @@ sealed class GlobalAction : Action {
|
|||
data class SetFeedbackManager(val feedbackManager: LegacyFeedbackManager) : GlobalAction()
|
||||
|
||||
data class SendEmail(val feedbackData: FeedbackData, val scanResponse: ScanResponse?) : GlobalAction()
|
||||
data class OpenChat(val feedbackData: FeedbackData, val chatConfig: ChatConfig? = null) : GlobalAction()
|
||||
|
||||
object ExchangeManager : GlobalAction() {
|
||||
object Init : GlobalAction() {
|
||||
|
|
|
|||
|
|
@ -74,26 +74,6 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is GlobalAction.OpenChat -> {
|
||||
val globalState = store.state.globalState
|
||||
val feedbackManager = globalState.feedbackManager.guard {
|
||||
store.dispatchDebugErrorNotification("FeedbackManager not initialized")
|
||||
return
|
||||
}
|
||||
val config = globalState.configManager?.config.guard {
|
||||
store.dispatchDebugErrorNotification("Config not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
// if config not set -> try to get it based on a scanResponse.productType
|
||||
val unsafeChatConfig = action.chatConfig ?: config.sprinklr
|
||||
|
||||
val chatConfig = unsafeChatConfig.guard {
|
||||
store.dispatchDebugErrorNotification("The chat config is not initialized")
|
||||
return
|
||||
}
|
||||
feedbackManager.openChat(chatConfig, action.feedbackData)
|
||||
}
|
||||
is GlobalAction.ExchangeManager.Init -> {
|
||||
val appStateSafe = appState() ?: return
|
||||
val config = appStateSafe.globalState.configManager?.config ?: return
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.data
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -30,7 +31,10 @@ internal class RuntimeUserWalletsStore(
|
|||
return userWalletsListManager.userWallets.firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet) {
|
||||
userWalletsListManager.update(userWalletId, update)
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
return userWalletsListManager.update(userWalletId, update)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
internal typealias Derivations = Map<ByteArrayKey, List<DerivationPath>>
|
||||
|
|
@ -48,13 +48,12 @@ internal class DefaultDerivationsRepository(
|
|||
|
||||
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
|
||||
.doOnSuccess { response ->
|
||||
updatePublicKeys(userWalletId = userWalletId, keys = response.entries).fold(
|
||||
onSuccess = {
|
||||
validateDerivations(userWallet.scanResponse, derivations)
|
||||
updatePublicKeys(userWalletId = userWalletId, keys = response.entries)
|
||||
.doOnSuccess {
|
||||
validateDerivations(scanResponse = it.scanResponse, derivations = derivations)
|
||||
return
|
||||
},
|
||||
onFailure = { throw it },
|
||||
)
|
||||
}
|
||||
.doOnFailure { throw it }
|
||||
}
|
||||
.doOnFailure { throw it }
|
||||
|
||||
|
|
@ -99,8 +98,8 @@ internal class DefaultDerivationsRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): Result<Unit> {
|
||||
return runCatching(dispatchers.io) {
|
||||
private suspend fun updatePublicKeys(userWalletId: UserWalletId, keys: DerivedKeys): CompletionResult<UserWallet> {
|
||||
return withContext(dispatchers.io) {
|
||||
userWalletsStore.update(
|
||||
userWalletId = userWalletId,
|
||||
update = { userWallet -> userWallet.updateDerivedKeys(keys) },
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ class DetailsMiddleware {
|
|||
deleteSavedAccessCodes()
|
||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
|
||||
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,10 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
|
||||
if (isResetCardAllowed) {
|
||||
CardInfo.ResetToFactorySettings(
|
||||
description = getResetToFactoryDescription(card.backupStatus, cardTypesResolver),
|
||||
description = getResetToFactoryDescription(
|
||||
isActiveBackupStatus = card.backupStatus?.isActive == true,
|
||||
typesResolver = cardTypesResolver,
|
||||
),
|
||||
).let(::add)
|
||||
}
|
||||
}
|
||||
|
|
@ -135,10 +138,15 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
push(
|
||||
route = AppRoute.ResetToFactory(
|
||||
userWalletId = userWalletId,
|
||||
cardSpecificInfo = AppRoute.ResetToFactory.CardSpecificInfo(
|
||||
cardId = card.cardId,
|
||||
backupStatus = card.backupStatus,
|
||||
),
|
||||
isActiveBackupStatus = card.backupStatus?.isActive == true,
|
||||
backupCardsCount = when (val status = card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
CardDTO.BackupStatus.NoBackup,
|
||||
null,
|
||||
-> 0
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
package com.tangem.tap.features.details.ui.common.utils
|
||||
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal fun getResetToFactoryDescription(
|
||||
backupStatus: CardDTO.BackupStatus?,
|
||||
isActiveBackupStatus: Boolean,
|
||||
typesResolver: CardTypesResolver,
|
||||
): TextReference {
|
||||
return if (backupStatus?.isActive != true || typesResolver.isTangemTwins()) {
|
||||
return if (!isActiveBackupStatus || 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)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ 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.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -65,8 +64,15 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
// endregion
|
||||
|
||||
// region Data of card that was scanned on CardSettings
|
||||
private val primaryCardId: String
|
||||
private val primaryBackupStatus: CardDTO.BackupStatus?
|
||||
private val primaryCardId: String = savedStateHandle.get<String>(AppRoute.ResetToFactory.CARD_ID)
|
||||
?: error("CardId must be provided for ResetCardViewModel")
|
||||
|
||||
private val isActiveBackupPrimaryCard =
|
||||
savedStateHandle.get<Boolean>(AppRoute.ResetToFactory.IS_ACTIVE_BACKUP_STATUS)
|
||||
?: error("IsActiveBackupCard must be provided for ResetCardViewModel")
|
||||
|
||||
private val primaryBackupCardsCount = savedStateHandle.get<Int>(AppRoute.ResetToFactory.BACKUP_CARDS_COUNT)
|
||||
?: error("CardCount must be provided for ResetCardViewModel")
|
||||
// endregion
|
||||
|
||||
// TODO: move logic to separate domain entity
|
||||
|
|
@ -76,15 +82,6 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
value = getInitialState(),
|
||||
)
|
||||
|
||||
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 {
|
||||
|
|
@ -98,7 +95,7 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
return ResetCardScreenState(
|
||||
resetButtonEnabled = false,
|
||||
descriptionText = getResetToFactoryDescription(
|
||||
backupStatus = primaryBackupStatus,
|
||||
isActiveBackupStatus = isActiveBackupPrimaryCard,
|
||||
typesResolver = currentCardTypesResolver,
|
||||
),
|
||||
warningsToShow = warningsToShow,
|
||||
|
|
@ -115,7 +112,7 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
private fun shouldShowResetPasswordButton(): Boolean {
|
||||
val isTangemWallet = currentCardTypesResolver.isTangemWallet() || currentCardTypesResolver.isWallet2()
|
||||
|
||||
return isTangemWallet && primaryBackupStatus is CardDTO.BackupStatus.Active
|
||||
return isTangemWallet && isActiveBackupPrimaryCard
|
||||
}
|
||||
|
||||
private fun toggleFirstCondition(isAccepted: Boolean) {
|
||||
|
|
@ -283,12 +280,6 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
private fun getBackupCardsCount(): Int {
|
||||
if (!currentCardTypesResolver.isMultiwalletAllowed()) return 0
|
||||
|
||||
return when (val status = primaryBackupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
is CardDTO.BackupStatus.NoBackup,
|
||||
null,
|
||||
-> 0
|
||||
}
|
||||
return primaryBackupCardsCount
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ internal object AttestationFailedDialog {
|
|||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_error)
|
||||
setMessage(R.string.issuer_signature_loading_failed)
|
||||
setPositiveButton(R.string.ok) { dialog, _ ->
|
||||
setPositiveButton(R.string.common_ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
setOnDismissListener {
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ internal class TokensListMigration(
|
|||
derivePublicKeysUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList)
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
.onLeft { Timber.e(it, "Failed to derive public keys") }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import androidx.lifecycle.viewModelScope
|
|||
import androidx.paging.*
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.getGreyedOutIconRes
|
||||
|
|
@ -26,7 +24,6 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
|||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.TokenWithBlockchain
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
|
||||
import com.tangem.tap.common.extensions.getNetworkName
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.SupportBlockchainType
|
||||
|
|
@ -326,7 +323,6 @@ internal class TokensListViewModel @Inject constructor(
|
|||
)
|
||||
|
||||
uiState = state.copy(isSavingInProgress = false)
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -144,7 +146,7 @@ internal class DefaultDerivationsRepositoryTest {
|
|||
coEvery { tangemSdkManager.derivePublicKeys(null, any()) } returns CompletionResult.Success(
|
||||
DerivationTaskResponse(DerivedKeysMocks.ethereumDerivedKeys),
|
||||
)
|
||||
coEvery { userWalletsStore.update(defaultUserWalletId, any()) } just Runs
|
||||
coEvery { userWalletsStore.update(defaultUserWalletId, any()) } returns CompletionResult.Success(userWallet)
|
||||
|
||||
runCatching {
|
||||
repository.derivePublicKeys(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ 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
|
||||
|
|
@ -145,25 +144,35 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data object AppSettings : AppRoute(path = "/app_settings")
|
||||
|
||||
/**
|
||||
* Reset to factory route
|
||||
* Reset to factory
|
||||
*
|
||||
* @property userWalletId user wallet id
|
||||
* @property cardSpecificInfo info about card that was scanned on CardSettings
|
||||
* @property cardId reset card id
|
||||
* @property isActiveBackupStatus reset backup card status
|
||||
* @property backupCardsCount backup cards count
|
||||
*/
|
||||
@Serializable
|
||||
data class ResetToFactory(
|
||||
val userWalletId: UserWalletId,
|
||||
val cardSpecificInfo: CardSpecificInfo,
|
||||
) : AppRoute(path = "/reset_to_factory/${userWalletId.stringValue}/$cardSpecificInfo"), RouteBundleParams {
|
||||
val cardId: String,
|
||||
val isActiveBackupStatus: Boolean,
|
||||
val backupCardsCount: Int,
|
||||
) : AppRoute(
|
||||
path = "/reset_to_factory" +
|
||||
"/${userWalletId.stringValue}" +
|
||||
"/$cardId" +
|
||||
"/$isActiveBackupStatus" +
|
||||
"/$backupCardsCount",
|
||||
),
|
||||
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"
|
||||
const val CARD_ID = "cardId"
|
||||
const val IS_ACTIVE_BACKUP_STATUS = "isActiveBackupStatus"
|
||||
const val BACKUP_CARDS_COUNT = "backupCardsCount"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.datasource.local.userwallet
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -14,6 +15,8 @@ interface UserWalletsStore {
|
|||
|
||||
suspend fun getAllSyncOrNull(): List<UserWallet>?
|
||||
|
||||
@Throws
|
||||
suspend fun update(userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet)
|
||||
suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet>
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@ 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
|
||||
|
||||
|
|
@ -300,19 +298,12 @@ 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
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ internal class DefaultDetailsComponent @AssistedInject constructor(
|
|||
private val model: DetailsModel = getOrCreateModel(params)
|
||||
|
||||
private val userWalletListComponent = userWalletListComponentFactory.create(
|
||||
context = child(key = "user_wallet_list"),
|
||||
context = child(key = "user_wallet_list_component"),
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import arrow.core.raise.*
|
||||
import arrow.core.recover
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import arrow.core.raise.fold
|
||||
import arrow.core.raise.recover
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.navigation.popTo
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.SimpleOkDialog
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.isNullOrEmpty
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.ContentMessage
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
|
|
@ -21,7 +26,7 @@ import com.tangem.domain.wallets.models.SaveWalletError
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase
|
||||
import com.tangem.features.details.impl.R
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -31,7 +36,7 @@ internal class UserWalletSaver @Inject constructor(
|
|||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val saveWalletUseCase: SaveWalletUseCase,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val router: Router,
|
||||
|
|
@ -43,8 +48,6 @@ internal class UserWalletSaver @Inject constructor(
|
|||
val userWallet = createUserWallet(response)
|
||||
|
||||
saveWallet(userWallet)
|
||||
|
||||
router.popTo<AppRoute.Wallet>()
|
||||
},
|
||||
recover = { error ->
|
||||
val message = error.message
|
||||
|
|
@ -56,9 +59,17 @@ internal class UserWalletSaver @Inject constructor(
|
|||
)
|
||||
|
||||
private suspend fun Raise<Error>.saveWallet(userWallet: UserWallet) {
|
||||
saveWalletUseCase(userWallet).recover { error ->
|
||||
fold(
|
||||
block = { saveWalletUseCase(userWallet).bind() },
|
||||
recover = { error ->
|
||||
when (error) {
|
||||
is SaveWalletError.WalletAlreadySaved -> selectUserWallet(userWallet)
|
||||
is SaveWalletError.WalletAlreadySaved -> {
|
||||
if (shouldSaveUserWalletsSyncUseCase()) {
|
||||
selectUserWallet()
|
||||
} else {
|
||||
router.popTo<AppRoute.Wallet>()
|
||||
}
|
||||
}
|
||||
is SaveWalletError.DataError -> {
|
||||
val messageRef = ensureNotNull(error.messageId?.let(::resourceReference)) {
|
||||
Error.Unknown
|
||||
|
|
@ -67,17 +78,25 @@ internal class UserWalletSaver @Inject constructor(
|
|||
raise(Error.Message(messageRef))
|
||||
}
|
||||
}
|
||||
}.bind()
|
||||
|
||||
},
|
||||
transform = {
|
||||
// call only if wallet is successfully saved
|
||||
reduxStateHolder.onUserWalletSelected(userWallet)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.selectUserWallet(userWallet: UserWallet) {
|
||||
withError({ Error.Unknown }) {
|
||||
selectWalletUseCase(userWallet.walletId).bind()
|
||||
}
|
||||
|
||||
router.popTo<AppRoute.Wallet>()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectUserWallet() {
|
||||
messageSender.send(
|
||||
message = ContentMessage { onDismiss ->
|
||||
SimpleOkDialog(
|
||||
message = stringResource(id = R.string.user_wallet_list_error_wallet_already_saved),
|
||||
onDismissDialog = onDismiss,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,11 @@ internal fun DisclaimerScreen(state: DisclaimerUM) {
|
|||
.background(backgroundColor)
|
||||
.statusBarsPadding(),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(bottom = bottomPadding)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(bottom = bottomPadding)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
title = resourceReference(R.string.disclaimer_title),
|
||||
startButton = TopAppBarButtonUM(
|
||||
|
|
@ -110,6 +114,7 @@ private fun DisclaimerContent(url: String, isTosAccepted: Boolean) {
|
|||
it.setBackgroundColor(backgroundColor.toArgb())
|
||||
},
|
||||
client = remember { DisclaimerWebViewClient() },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
|
|
@ -117,6 +122,9 @@ private fun DisclaimerContent(url: String, isTosAccepted: Boolean) {
|
|||
label = "Loading state change animation",
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import androidx.compose.material.pullrefresh.pullRefresh
|
|||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.ScaffoldDefaults
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -32,6 +31,7 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlock
|
|||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.notifications.OkxPromoNotification
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.components.transactions.txHistoryItems
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
|
|
@ -64,7 +64,16 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
|
|||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
Scaffold(
|
||||
topBar = { TokenDetailsTopAppBar(config = state.topAppBarConfig) },
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
snackbarHost = {
|
||||
TangemSnackbarHost(
|
||||
modifier = Modifier.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
bottom = bottomBarHeight + TangemTheme.dimens.spacing16,
|
||||
),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
},
|
||||
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars),
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
) { scaffoldPaddings ->
|
||||
|
|
|
|||
|
|
@ -368,7 +368,9 @@ private fun BaseScaffoldWithMarkets(
|
|||
WalletSnackbarHost(
|
||||
snackbarHostState = it,
|
||||
event = state.event,
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing16),
|
||||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens.spacing4)
|
||||
.navigationBarsPadding(),
|
||||
)
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
|
|
|
|||
|
|
@ -341,6 +341,8 @@ internal class WalletViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) {
|
||||
walletScreenContentLoader.cancel(action.deletedWalletId)
|
||||
|
||||
walletScreenContentLoader.load(
|
||||
userWallet = action.selectedWallet,
|
||||
clickIntents = clickIntents,
|
||||
|
|
|
|||
|
|
@ -254,7 +254,6 @@ reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", versio
|
|||
walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" }
|
||||
walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" }
|
||||
prettyLogger = { module = "com.orhanobut:logger", version.ref = "prettyLogger" }
|
||||
sprClient = { module = "com.spr:messengerclient", version.ref = "spr-client" }
|
||||
chucker = { module = "com.github.chuckerteam.chucker:library", version.ref = "chucker" }
|
||||
chuckerStub = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" }
|
||||
mlKit-barcodeScanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "mlKit-barcodeScanning" }
|
||||
|
|
|
|||
|
|
@ -65,9 +65,7 @@ private fun AppExtension.configureBuildTypes() {
|
|||
|
||||
private fun AndroidBuildType.configureBuildVariant(extension: AppExtension, buildType: BuildType) {
|
||||
when (buildType) {
|
||||
BuildType.Release,
|
||||
BuildType.External,
|
||||
-> {
|
||||
BuildType.Release -> {
|
||||
isDebuggable = false
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(extension.getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
|
|
@ -76,6 +74,11 @@ private fun AndroidBuildType.configureBuildVariant(extension: AppExtension, buil
|
|||
isDebuggable = true
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
BuildType.External -> {
|
||||
initWith(extension.buildTypes.getByName(BuildType.Release.id))
|
||||
matchingFallbacks.add(BuildType.Release.id)
|
||||
signingConfig = extension.signingConfigs.getByName(BuildType.Debug.id)
|
||||
}
|
||||
BuildType.Internal,
|
||||
BuildType.Mocked,
|
||||
-> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue