diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 35083a7d65..bbe71b61be 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -201,8 +201,8 @@ dependencies {
/** Other libraries */
implementation(deps.kotlin.immutable.collections)
implementation(deps.material)
- implementation(deps.googlePlay.core)
- implementation(deps.googlePlay.core.ktx)
+ implementation(deps.googlePlay.review)
+ implementation(deps.googlePlay.review.ktx)
implementation(deps.googlePlay.services.wallet)
coreLibraryDesugaring(deps.desugar)
implementation(deps.timber)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 02e790d2c8..aed18d1c66 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -20,6 +20,7 @@
+
diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt
index 2c3936c358..fd2967ed70 100644
--- a/app/src/main/java/com/tangem/tap/MainActivity.kt
+++ b/app/src/main/java/com/tangem/tap/MainActivity.kt
@@ -1,12 +1,9 @@
package com.tangem.tap
import android.annotation.SuppressLint
-import android.app.PendingIntent
import android.content.Intent
-import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.content.res.Configuration
-import android.nfc.NfcAdapter
import android.os.Bundle
import android.view.View
import androidx.activity.SystemBarStyle
@@ -358,36 +355,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
override fun onResume() {
super.onResume()
- val nfcAdapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
- if (nfcAdapter?.isEnabled == true) {
- val pendingIntent = PendingIntent.getActivity(
- this,
- 0,
- Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
- PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
- )
- val intentFilters = arrayOf(
- IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
- IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED),
- IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED),
- )
- nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null)
- }
// TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
navigateToInitialScreenIfNeeded(intent)
}
- override fun onPause() {
- super.onPause()
- val nfcAdapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
-
- if (nfcAdapter?.isEnabled == true) {
- nfcAdapter.disableForegroundDispatch(this)
- }
- }
-
override fun onStop() {
notificationsHandler = null
dialogManager.onStop()
diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
index 936230d205..829fdd5601 100644
--- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt
@@ -143,8 +143,9 @@ class DialogManager : StoreSubscriber {
title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
message = context.getString(
state.dialog.messageRes,
- state.dialog.currencySymbol,
state.dialog.currencyTitle,
+ state.dialog.currencySymbol,
+ state.dialog.networkName,
),
context = context,
)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt
index b896303e37..04a76036c3 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt
@@ -43,6 +43,7 @@ sealed class AppDialog : StateDialog {
data class TokensAreLinkedDialog(
val currencyTitle: String,
val currencySymbol: String,
+ val networkName: String,
) : AppDialog() {
val messageRes: Int = R.string.token_details_unable_hide_alert_message
val titleRes: Int = R.string.token_details_unable_hide_alert_title
diff --git a/app/src/main/java/com/tangem/tap/data/DefaultCardSdkProvider.kt b/app/src/main/java/com/tangem/tap/data/DefaultCardSdkProvider.kt
index a164e0dde1..395f26e868 100644
--- a/app/src/main/java/com/tangem/tap/data/DefaultCardSdkProvider.kt
+++ b/app/src/main/java/com/tangem/tap/data/DefaultCardSdkProvider.kt
@@ -19,6 +19,8 @@ import com.tangem.sdk.extensions.*
import com.tangem.sdk.nfc.NfcManager
import com.tangem.sdk.storage.create
import com.tangem.tap.foregroundActivityObserver
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.runBlocking
import javax.inject.Inject
import javax.inject.Singleton
@@ -30,6 +32,7 @@ import javax.inject.Singleton
@Singleton
internal class DefaultCardSdkProvider @Inject constructor(
private val analyticsEventHandler: AnalyticsEventHandler,
+ private val dispatchers: CoroutineDispatcherProvider,
) : CardSdkProvider, CardSdkOwner {
private val observer = Observer()
@@ -39,12 +42,12 @@ internal class DefaultCardSdkProvider @Inject constructor(
override val sdk: TangemSdk
get() = holder?.sdk ?: tryToRegisterWithForegroundActivity()
- override fun register(activity: FragmentActivity) {
+ override fun register(activity: FragmentActivity) = runBlocking(dispatchers.mainImmediate) {
if (activity.isDestroyed || activity.isFinishing || activity.isChangingConfigurations) {
val message = "Tangem SDK owner registration skipped: activity is destroyed or finishing"
analyticsEventHandler.send(TangemSdkWarningEvent(message))
Log.info { message }
- return
+ return@runBlocking
}
if (holder != null) {
@@ -58,7 +61,7 @@ internal class DefaultCardSdkProvider @Inject constructor(
Log.info { "Tangem SDK owner registered" }
}
- private fun tryToRegisterWithForegroundActivity(): TangemSdk {
+ private fun tryToRegisterWithForegroundActivity(): TangemSdk = runBlocking(dispatchers.mainImmediate) {
val warning = "Tangem SDK holder is null, trying to recreate it with foreground activity"
analyticsEventHandler.send(TangemSdkWarningEvent(warning))
Log.warning { warning }
@@ -83,7 +86,7 @@ internal class DefaultCardSdkProvider @Inject constructor(
error(error)
}
- return sdk
+ return@runBlocking sdk
}
private fun initialize(activity: FragmentActivity) {
diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt
index e3de1dc4a7..0f5e408f5e 100644
--- a/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/sdk/TangemSdkManager.kt
@@ -33,6 +33,10 @@ interface TangemSdkManager {
val userCodeRequestPolicy: UserCodeRequestPolicy
+ suspend fun checkCanUseBiometry(awaitInitialization: Boolean = true): Boolean
+
+ suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean = true): Boolean
+
suspend fun scanProduct(
cardId: String? = null,
messageRes: Int? = null,
diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt
index 72b578cea9..00bacdbe26 100644
--- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt
@@ -3,9 +3,11 @@ package com.tangem.tap.domain.sdk.impl
import android.content.res.Resources
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
+import com.tangem.Log
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.common.*
+import com.tangem.common.authentication.AuthenticationManager
import com.tangem.common.authentication.keystore.KeystoreManager
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.*
@@ -38,16 +40,21 @@ import com.tangem.tap.domain.twins.CreateSecondTwinWalletTask
import com.tangem.tap.domain.twins.FinalizeTwinTask
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
-@Suppress("TooManyFunctions")
+@Suppress("TooManyFunctions", "LargeClass")
class DefaultTangemSdkManager(
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val resources: Resources,
) : TangemSdkManager {
+ private val awaitInitializationMutex = Mutex()
+
private val tangemSdk: TangemSdk
get() = cardSdkConfigRepository.sdk
@@ -73,6 +80,42 @@ class DefaultTangemSdkManager(
override val userCodeRequestPolicy: UserCodeRequestPolicy
get() = tangemSdk.config.userCodeRequestPolicy
+ override suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean): Boolean {
+ return try {
+ needEnrollBiometrics
+ } catch (e: TangemSdkError.AuthenticationNotInitialized) {
+ Log.error {
+ "Trying to access `needEnrollBiometrics` flag when authentication manager is not initialized: " +
+ if (awaitInitialization) "awaiting initialization" else "failing"
+ }
+
+ if (awaitInitialization) {
+ awaitAuthenticationManagerInitialization().needEnrollBiometrics
+ } else {
+ throw e
+ }
+ }
+ }
+
+ override suspend fun checkCanUseBiometry(awaitInitialization: Boolean): Boolean {
+ return try {
+ canUseBiometry
+ } catch (e: TangemSdkError.AuthenticationNotInitialized) {
+ Log.error {
+ "Trying to access `canUseBiometry` flag when authentication manager is not initialized: " +
+ if (awaitInitialization) "awaiting initialization" else "failing"
+ }
+
+ if (awaitInitialization) {
+ val manager = awaitAuthenticationManagerInitialization()
+
+ manager.canAuthenticate || manager.needEnrollBiometrics
+ } else {
+ throw e
+ }
+ }
+ }
+
override suspend fun scanProduct(
cardId: String?,
messageRes: Int?,
@@ -288,6 +331,26 @@ class DefaultTangemSdkManager(
tangemSdk.config.userCodeRequestPolicy = policy
}
+ private suspend fun awaitAuthenticationManagerInitialization(): AuthenticationManager {
+ return awaitInitializationMutex.withLock {
+ var attemps = 0
+
+ do {
+ if (tangemSdk.authenticationManager.isInitialized) {
+ break
+ } else {
+ if (attemps++ >= MAX_INITIALIZE_ATTEMPTS) {
+ error("Can't initialize authentication manager after $MAX_INITIALIZE_ATTEMPTS attempts")
+ } else {
+ delay(timeMillis = 200)
+ }
+ }
+ } while (true)
+
+ tangemSdk.authenticationManager
+ }
+ }
+
// region Twin-specific
override suspend fun createFirstTwinWallet(
@@ -335,6 +398,8 @@ class DefaultTangemSdkManager(
// endregion
companion object {
+ private const val MAX_INITIALIZE_ATTEMPTS = 10
+
@Deprecated("Use [DefaultCardSdkProvider] instead")
val config = Config(
linkedTerminal = true,
diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt
index 7c5585c68b..dcc850f798 100644
--- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt
@@ -28,9 +28,11 @@ class MockTangemSdkManager(
private val resources: Resources,
) : TangemSdkManager {
- override val canUseBiometry = false
+ private var userCodeRequestPolicyInternal: UserCodeRequestPolicy = UserCodeRequestPolicy.Default
- override val needEnrollBiometrics = false
+ override val canUseBiometry: Boolean = false
+
+ override val needEnrollBiometrics: Boolean = false
override val keystoreManager = DummyKeystoreManager()
@@ -39,7 +41,9 @@ class MockTangemSdkManager(
override val userCodeRequestPolicy: UserCodeRequestPolicy
get() = userCodeRequestPolicyInternal
- private var userCodeRequestPolicyInternal: UserCodeRequestPolicy = UserCodeRequestPolicy.Default
+ override suspend fun checkCanUseBiometry(awaitInitialization: Boolean): Boolean = canUseBiometry
+
+ override suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean): Boolean = needEnrollBiometrics
override suspend fun scanProduct(
cardId: String?,
diff --git a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt
index 00f7f53887..6a84f8cd30 100644
--- a/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/settings/DefaultLegacySettingsRepository.kt
@@ -7,5 +7,5 @@ internal class DefaultLegacySettingsRepository(
private val tangemSdkManager: TangemSdkManager,
) : LegacySettingsRepository {
- override fun canUseBiometry(): Boolean = tangemSdkManager.canUseBiometry
+ override suspend fun canUseBiometry(): Boolean = tangemSdkManager.checkCanUseBiometry()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
index bde329b131..c9dc2ceead 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt
@@ -37,7 +37,10 @@ import com.tangem.utils.coroutines.saveIn
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
@@ -267,7 +270,6 @@ class DetailsMiddleware {
.onEach { needEnrollBiometrics ->
store.dispatchWithMain(DetailsAction.AppSettings.BiometricsStatusChanged(needEnrollBiometrics))
}
- .flowOn(Dispatchers.IO)
.launchIn(lifecycleScope)
.saveIn(checkBiometricsStatusJobHolder)
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
index 837fd35328..7a87450f97 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsReducer.kt
@@ -78,7 +78,9 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppS
},
createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup,
appSettingsState = AppSettingsState(
- isBiometricsAvailable = tangemSdkManager.canUseBiometry,
+ isBiometricsAvailable = runBlocking {
+ tangemSdkManager.checkCanUseBiometry()
+ },
saveWallets = action.shouldSaveUserWallets,
saveAccessCodes = runBlocking {
store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
index f8fb8f2ba8..d6c4adff57 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt
@@ -99,7 +99,7 @@ object OnboardingHelper {
}
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
// then open save wallet screen
- tangemSdkManager.canUseBiometry && settingsRepository.shouldShowSaveUserWalletScreen() -> {
+ tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
delay(timeMillis = 1_200)
diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt
index 82799c76e6..f1b43ae123 100644
--- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt
@@ -84,10 +84,10 @@ internal class SaveWalletMiddleware {
activityResultCaller.openSystemBiometrySettings()
}
- private fun allowToUseBiometrics(state: SaveWalletState) {
- if (tangemSdkManager.needEnrollBiometrics) {
- store.dispatchOnMain(SaveWalletAction.EnrollBiometrics)
- return
+ private fun allowToUseBiometrics(state: SaveWalletState) = scope.launch {
+ if (tangemSdkManager.checkNeedEnrollBiometrics()) {
+ store.dispatchWithMain(SaveWalletAction.EnrollBiometrics)
+ return@launch
}
if (state.backupInfo != null) {
@@ -97,24 +97,22 @@ internal class SaveWalletMiddleware {
Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On))
}
- scope.launch {
- /*
+ /*
- * because it will be automatically saved on UserWalletsListManager switch
- */
- val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
- val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
- val error = IllegalStateException("No selected user wallet")
- Timber.e(error, "Unable to save user wallet")
- store.dispatchWithMain(
- SaveWalletAction.AllowToUseBiometrics.Error(TangemSdkError.ExceptionError(error)),
- )
- return@launch
- }
+ * because it will be automatically saved on UserWalletsListManager switch
+ */
+ val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
+ val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
+ val error = IllegalStateException("No selected user wallet")
+ Timber.e(error, "Unable to save user wallet")
+ store.dispatchWithMain(
+ SaveWalletAction.AllowToUseBiometrics.Error(TangemSdkError.ExceptionError(error)),
+ )
+ return@launch
+ }
- handleSuccessAllowing(selectedUserWallet)
- }.saveIn(saveWalletJobHolder)
- }
+ handleSuccessAllowing(selectedUserWallet)
+ }.saveIn(saveWalletJobHolder)
private suspend fun handleSuccessAllowing(userWallet: UserWallet) {
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
index a62f01e74b..987034d99b 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
@@ -31,9 +31,13 @@ internal class DefaultTokensListRouter : TokensListRouter {
store.dispatchNotification(R.string.contract_address_copied_message)
}
- override fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String) {
+ override fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String, networkName: String) {
store.dispatchDialogShow(
- dialog = AppDialog.TokensAreLinkedDialog(currencyTitle = tokenName, currencySymbol = tokenSymbol),
+ dialog = AppDialog.TokensAreLinkedDialog(
+ currencyTitle = tokenName,
+ currencySymbol = tokenSymbol,
+ networkName = networkName,
+ ),
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt
index 34b5379b48..a11e584564 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/TokensListRouter.kt
@@ -19,8 +19,9 @@ internal interface TokensListRouter {
*
* @param tokenName token name
* @param tokenSymbol token brief name
+ * @param networkName blockchain network full name
*/
- fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String)
+ fun openUnableHideMainTokenAlert(tokenName: String, tokenSymbol: String, networkName: String)
/**
* Open alert to remove wallet
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt
index f5dc7f92b4..a716c916bb 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/viewmodels/TokensListViewModel.kt
@@ -365,6 +365,7 @@ internal class TokensListViewModel @Inject constructor(
router.openUnableHideMainTokenAlert(
tokenName = blockchain.name,
tokenSymbol = blockchain.currency,
+ networkName = blockchain.fullName,
)
} else if (isAddedOnMainScreen) {
router.openRemoveWalletAlert(
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseCallDelegate.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseCallDelegate.kt
index 52a2e4cc6f..919be8e848 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseCallDelegate.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseCallDelegate.kt
@@ -5,6 +5,7 @@ import okio.Timeout
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
+import timber.log.Timber
internal class ApiResponseCallDelegate(
private val wrappedCall: Call,
@@ -20,7 +21,9 @@ internal class ApiResponseCallDelegate(
override fun timeout(): Timeout = wrappedCall.timeout()
override fun isExecuted(): Boolean = wrappedCall.isExecuted
override fun isCanceled(): Boolean = wrappedCall.isCanceled
- override fun cancel() { wrappedCall.cancel() }
+ override fun cancel() {
+ wrappedCall.cancel()
+ }
private inner class ApiResponseCallback(
private val responseCallback: Callback>,
@@ -33,7 +36,15 @@ internal class ApiResponseCallDelegate(
}
override fun onFailure(call: Call, t: Throwable) {
- val error = t.toApiError()
+ val error = try {
+ t.toApiError()
+ } catch (e: ApiResponseError) {
+ Timber.e(e, "error map toApiError")
+ e
+ } catch (e: Exception) {
+ Timber.e(e, "onFailure UnknownException")
+ ApiResponseError.UnknownException(e)
+ }
val safeResponse = apiError(error)
responseCallback.onResponse(this@ApiResponseCallDelegate, Response.success(safeResponse))
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseError.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseError.kt
index cfd2146441..22162b76b4 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseError.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ApiResponseError.kt
@@ -73,10 +73,16 @@ sealed class ApiResponseError : Exception() {
}
/** Represents a network error, typically when there's no connectivity. */
- data object NetworkException : ApiResponseError()
+ @Suppress("UnusedPrivateMember")
+ data object NetworkException : ApiResponseError() {
+ private fun readResolve(): Any = NetworkException
+ }
/** Represents a timeout error, typically when the server takes too long to respond. */
- data object TimeoutException : ApiResponseError()
+ @Suppress("UnusedPrivateMember")
+ data object TimeoutException : ApiResponseError() {
+ private fun readResolve(): Any = TimeoutException
+ }
/**
* Represents an unexpected exception that doesn't fall into one of the other categories.
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ResponseExt.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ResponseExt.kt
index 92a5af0ac5..f696275bc3 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ResponseExt.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/response/ResponseExt.kt
@@ -2,6 +2,7 @@ package com.tangem.datasource.api.common.response
import kotlinx.coroutines.TimeoutCancellationException
import retrofit2.Response
+import timber.log.Timber
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
@@ -16,10 +17,15 @@ internal fun Response.toSafeApiResponse(): ApiResponse {
} else {
val code = ApiResponseError.HttpException.Code.values
.firstOrNull { it.code == code() }
- val e = if (code == null) {
- ApiResponseError.UnknownException(IllegalArgumentException("Unknown error status code: ${code()}"))
- } else {
- ApiResponseError.HttpException(code, message(), errorBody()?.string())
+ val e = try {
+ if (code == null) {
+ ApiResponseError.UnknownException(IllegalArgumentException("Unknown error status code: ${code()}"))
+ } else {
+ ApiResponseError.HttpException(code, message(), errorBody()?.string())
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "UnknownException occured")
+ ApiResponseError.UnknownException(e)
}
apiError(e)
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt
index 076c60da41..34ae262e6d 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationWithBackground.kt
@@ -172,7 +172,7 @@ private class NotificationWithBackgroundPreviewProvider : PreviewParameterProvid
title = resourceReference(id = R.string.main_swap_changelly_promotion_title),
subtitle = resourceReference(
id = R.string.main_swap_changelly_promotion_message,
- formatArgs = wrappedList("1", "2"),
+ formatArgs = wrappedList("1", "2", "3"),
),
iconResId = R.drawable.img_swap_promo,
backgroundResId = R.drawable.img_swap_promo_blue_banner_background,
@@ -180,7 +180,7 @@ private class NotificationWithBackgroundPreviewProvider : PreviewParameterProvid
NotificationConfig(
title = resourceReference(
id = R.string.token_swap_changelly_promotion_title,
- formatArgs = wrappedList("1", "2"),
+ formatArgs = wrappedList("1"),
),
subtitle = stringReference(
"Swap multiple currencies between any chains you wish. Swap multiple " +
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt
index 9d6d403946..eacaa9567e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt
@@ -58,7 +58,7 @@ private fun Content(config: NotificationConfig) {
.align(Alignment.CenterVertically),
)
Column(
- verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
modifier = Modifier
.weight(1f)
.padding(TangemTheme.dimens.spacing12),
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsPagingSource.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsPagingSource.kt
index e5c4c36e1a..9cf7b0092c 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsPagingSource.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/paging/CoinsPagingSource.kt
@@ -52,19 +52,23 @@ internal class CoinsPagingSource(
suffix = CryptoCurrency.ID.Suffix.RawID(coin.id),
)
}
- val quotes = quotesRepository.getQuotesSync(currenciesIds = coinsIds.toSet(), refresh = false)
- LoadResult.Page(
- data = CoinsResponseConverter.convert(
- CoinsData(
- response.coins,
- response.imageHost,
- quotes,
+ try {
+ val quotes = quotesRepository.getQuotesSync(currenciesIds = coinsIds.toSet(), refresh = false)
+ LoadResult.Page(
+ data = CoinsResponseConverter.convert(
+ CoinsData(
+ response.coins,
+ response.imageHost,
+ quotes,
+ ),
),
- ),
- prevKey = if (page == 0) null else page.minus(other = 1),
- nextKey = if (response.coins.isEmpty()) null else page.plus(other = 1),
- )
+ prevKey = if (page == 0) null else page.minus(other = 1),
+ nextKey = if (response.coins.isEmpty()) null else page.plus(other = 1),
+ )
+ } catch (t: Throwable) {
+ LoadResult.Error(t)
+ }
},
onFailure = { LoadResult.Error(it) },
)
diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt
index 390c70dcc4..880f941db8 100644
--- a/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt
+++ b/domain/settings/src/main/java/com/tangem/domain/settings/CanUseBiometryUseCase.kt
@@ -4,5 +4,5 @@ import com.tangem.domain.settings.repositories.LegacySettingsRepository
class CanUseBiometryUseCase(private val legacySettingsRepository: LegacySettingsRepository) {
- operator fun invoke(): Boolean = legacySettingsRepository.canUseBiometry()
+ suspend operator fun invoke(): Boolean = legacySettingsRepository.canUseBiometry()
}
\ No newline at end of file
diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt
index 9e1dc7cf3a..e1f1b8ebb7 100644
--- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt
+++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/LegacySettingsRepository.kt
@@ -2,5 +2,5 @@ package com.tangem.domain.settings.repositories
interface LegacySettingsRepository {
- fun canUseBiometry(): Boolean
+ suspend fun canUseBiometry(): Boolean
}
\ No newline at end of file
diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
index 1e04778c76..7ce6c78a7e 100644
--- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
+++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt
@@ -1821,13 +1821,21 @@ internal class SwapInteractorImpl @Inject constructor(
}
private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map {
- val set = quotesRepository.getQuotesSync(ids.toSet(), false)
+ val set = ids.toSet().getQuotesOrEmpty(false)
return ids
.mapNotNull { id -> set.find { it.rawCurrencyId == id.rawCurrencyId }?.let { id to it } }
.toMap()
}
+ private suspend fun Set.getQuotesOrEmpty(refresh: Boolean): Set {
+ return try {
+ quotesRepository.getQuotesSync(this, refresh)
+ } catch (t: Throwable) {
+ emptySet()
+ }
+ }
+
private fun getDemoFees(cryptoCurrency: CryptoCurrency): ProxyFees.MultipleFees {
val demoFee = ProxyAmount(
currencySymbol = cryptoCurrency.symbol,
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt
index e77952c869..fb15bd476d 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt
@@ -70,7 +70,8 @@ internal class ExchangeStatusFactory(
.map { savedTransactions ->
val quotes = savedTransactions
?.flatMap { setOf(it.fromCryptoCurrency.id, it.toCryptoCurrency.id) }
- ?.let { quotesRepository.getQuotesSync(it.toSet(), true) }
+ ?.toSet()
+ ?.getQuotesOrEmpty(true)
?: emptySet()
getExchangeStatusState(
@@ -176,4 +177,12 @@ internal class ExchangeStatusFactory(
else -> null
}
}
+
+ private suspend fun Set.getQuotesOrEmpty(refresh: Boolean): Set {
+ return try {
+ quotesRepository.getQuotesSync(this, refresh)
+ } catch (t: Throwable) {
+ emptySet()
+ }
+ }
}
\ No newline at end of file
diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts
index c96d1c74b4..9d6ff75448 100644
--- a/features/wallet/impl/build.gradle.kts
+++ b/features/wallet/impl/build.gradle.kts
@@ -33,7 +33,7 @@ dependencies {
/** Other libraries */
implementation(deps.arrow.core)
- implementation(deps.googlePlay.core)
+ implementation(deps.googlePlay.review)
implementation(deps.jodatime)
implementation(deps.kotlin.immutable.collections)
implementation(deps.reKotlin)
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ReviewManagerRequester.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ReviewManagerRequester.kt
index a52e61dd42..1826afb74d 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ReviewManagerRequester.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/utils/ReviewManagerRequester.kt
@@ -3,10 +3,10 @@ package com.tangem.feature.wallet.presentation.wallet.ui.utils
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
+import com.google.android.gms.tasks.Task
import com.google.android.play.core.review.ReviewInfo
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
-import com.google.android.play.core.tasks.Task
import timber.log.Timber
internal object ReviewManagerRequester {
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index e8dbee94ec..498657aa3e 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -47,8 +47,8 @@ coroutine = "1.7.2"
desugarJdkLibs = "1.1.5"
firebase = "33.1.0"
googleMaterialComponent = "1.6.1"
-googlePlayCore = "1.10.3"
-googlePlayCoreKtx = "1.8.1"
+googlePlayReview = "2.0.1"
+googlePlayReviewKtx = "2.0.1"
googlePlayServicesWallet = "19.1.0"
hilt = "2.46"
hilt-navigation = "1.0.0"
@@ -90,7 +90,7 @@ markdown = "0.7.2"
# region Tangem
tangemBlockchainSdk = "develop-700"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
-tangemCardSdk = "develop-370"
+tangemCardSdk = "develop-374"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
tangemVico = "2.0.0-alpha.21-tangem14"
#tangemVico = "0.0.1" # Keep it! - used for local builds ^
@@ -222,8 +222,8 @@ kotlin-coroutines-rx2 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2
kotlin-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutine" }
kotlin-immutable-collections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlin-immutable-collections" }
desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" }
-googlePlay-core = { module = "com.google.android.play:core", version.ref = "googlePlayCore" }
-googlePlay-core-ktx = { module = "com.google.android.play:core-ktx", version.ref = "googlePlayCoreKtx" }
+googlePlay-review = { module = "com.google.android.play:review", version.ref = "googlePlayReview" }
+googlePlay-review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "googlePlayReviewKtx" }
googlePlay-services-wallet = { module = "com.google.android.gms:play-services-wallet", version.ref = "googlePlayServicesWallet" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" }