Updated on 2026-08-14

This commit is contained in:
Tangem 2024-07-18 11:50:07 +01:00
commit ab5befb1a3
30 changed files with 203 additions and 99 deletions

View file

@ -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)

View file

@ -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" />

View file

@ -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()

View file

@ -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,
)

View file

@ -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

View file

@ -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) {

View file

@ -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,

View file

@ -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,

View file

@ -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?,

View file

@ -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()
}

View file

@ -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)
}

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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,
),
)
}

View file

@ -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

View file

@ -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(

View file

@ -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))

View file

@ -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.

View file

@ -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)

View file

@ -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 " +

View file

@ -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),

View file

@ -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) },
)

View file

@ -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()
}

View file

@ -2,5 +2,5 @@ package com.tangem.domain.settings.repositories
interface LegacySettingsRepository {
fun canUseBiometry(): Boolean
suspend fun canUseBiometry(): Boolean
}

View file

@ -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,

View file

@ -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()
}
}
}

View file

@ -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)

View file

@ -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 {

View file

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