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" }