Updated on 2026-08-14
This commit is contained in:
commit
ab5befb1a3
30 changed files with 203 additions and 99 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -143,8 +143,9 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import okio.Timeout
|
|||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import timber.log.Timber
|
||||
|
||||
internal class ApiResponseCallDelegate<T : Any>(
|
||||
private val wrappedCall: Call<T>,
|
||||
|
|
@ -20,7 +21,9 @@ internal class ApiResponseCallDelegate<T : Any>(
|
|||
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<ApiResponse<T>>,
|
||||
|
|
@ -33,7 +36,15 @@ internal class ApiResponseCallDelegate<T : Any>(
|
|||
}
|
||||
|
||||
override fun onFailure(call: Call<T>, 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<T>(error)
|
||||
|
||||
responseCallback.onResponse(this@ApiResponseCallDelegate, Response.success(safeResponse))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 <T : Any> Response<T>.toSafeApiResponse(): ApiResponse<T> {
|
|||
} 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)
|
||||
|
|
|
|||
|
|
@ -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 " +
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -2,5 +2,5 @@ package com.tangem.domain.settings.repositories
|
|||
|
||||
interface LegacySettingsRepository {
|
||||
|
||||
fun canUseBiometry(): Boolean
|
||||
suspend fun canUseBiometry(): Boolean
|
||||
}
|
||||
|
|
@ -1821,13 +1821,21 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map<CryptoCurrency.ID, Quote> {
|
||||
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<CryptoCurrency.ID>.getQuotesOrEmpty(refresh: Boolean): Set<Quote> {
|
||||
return try {
|
||||
quotesRepository.getQuotesSync(this, refresh)
|
||||
} catch (t: Throwable) {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDemoFees(cryptoCurrency: CryptoCurrency): ProxyFees.MultipleFees {
|
||||
val demoFee = ProxyAmount(
|
||||
currencySymbol = cryptoCurrency.symbol,
|
||||
|
|
|
|||
|
|
@ -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<CryptoCurrency.ID>.getQuotesOrEmpty(refresh: Boolean): Set<Quote> {
|
||||
return try {
|
||||
quotesRepository.getQuotesSync(this, refresh)
|
||||
} catch (t: Throwable) {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue