Updated on 2026-08-14
This commit is contained in:
parent
caf5b6fae7
commit
f2dfe2f8bc
21 changed files with 326 additions and 6 deletions
|
|
@ -78,6 +78,7 @@ dependencies {
|
|||
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.google)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.navigation)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ import com.tangem.domain.apptheme.model.AppThemeMode
|
|||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.settings.SetGooglePayAvailabilityUseCase
|
||||
import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase
|
||||
import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.staking.SendUnsubmittedHashesUseCase
|
||||
|
|
@ -70,6 +72,7 @@ import com.tangem.tap.common.SnackbarHandler
|
|||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
|
|
@ -83,6 +86,7 @@ import com.tangem.tap.proxy.AppStateHolder
|
|||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.tap.routing.RoutingComponent
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ActivityMainBinding
|
||||
|
|
@ -195,6 +199,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var onboardingV2FeatureToggles: OnboardingV2FeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var setGoogleServicesAvailabilityUseCase: SetGoogleServicesAvailabilityUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var setGooglePayAvailabilityUseCase: SetGooglePayAvailabilityUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var dispatchers: CoroutineDispatcherProvider
|
||||
|
||||
internal val viewModel: MainViewModel by viewModels()
|
||||
|
||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||
|
|
@ -244,6 +257,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
observeStateUpdates()
|
||||
observePolkadotAccountHealthCheck()
|
||||
sendStakingUnsubmittedHashes()
|
||||
checkGoogleServicesAvailability()
|
||||
|
||||
if (intent != null) {
|
||||
deepLinksRegistry.launch(intent)
|
||||
|
|
@ -605,6 +619,22 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
}
|
||||
|
||||
private fun checkGoogleServicesAvailability() {
|
||||
val isGoogleServicesAvailable = GoogleServicesHelper.checkGoogleServicesAvailability(this)
|
||||
|
||||
lifecycleScope.launch {
|
||||
setGoogleServicesAvailabilityUseCase(isGoogleServicesAvailable)
|
||||
|
||||
if (isGoogleServicesAvailable) {
|
||||
val paymentsClient = GoogleServicesHelper.createPaymentsClient(this@MainActivity)
|
||||
val isGooglePayAvailable = GoogleServicesHelper.checkGooglePayAvailability(paymentsClient)
|
||||
setGooglePayAvailabilityUseCase(isGooglePayAvailable)
|
||||
} else {
|
||||
setGooglePayAvailabilityUseCase(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val APP_THEME_LOAD_TIMEOUT = 2
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.domain.onramp.*
|
|||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -123,8 +124,14 @@ internal object OnrampDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampQuotesUseCase(onrampRepository: OnrampRepository): GetOnrampQuotesUseCase {
|
||||
return GetOnrampQuotesUseCase(onrampRepository)
|
||||
fun provideGetOnrampQuotesUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
onrampRepository: OnrampRepository,
|
||||
): GetOnrampQuotesUseCase {
|
||||
return GetOnrampQuotesUseCase(
|
||||
settingsRepository = settingsRepository,
|
||||
repository = onrampRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -226,4 +226,36 @@ internal object SettingsDomainModule {
|
|||
fun provideGetUserCountryCodeUseCase(settingsRepository: SettingsRepository): GetUserCountryUseCase {
|
||||
return GetUserCountryUseCase(settingsRepository)
|
||||
}
|
||||
|
||||
// region Google services availability
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSetGoogleServicesAvailabilityUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
): SetGoogleServicesAvailabilityUseCase {
|
||||
return SetGoogleServicesAvailabilityUseCase(settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsGoogleServicesAvailableUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
): IsGoogleServicesAvailableUseCase {
|
||||
return IsGoogleServicesAvailableUseCase(settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSetGooglePayAvailabilityUseCase(
|
||||
settingsRepository: SettingsRepository,
|
||||
): SetGooglePayAvailabilityUseCase {
|
||||
return SetGooglePayAvailabilityUseCase(settingsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsGooglePayAvailableUseCase(settingsRepository: SettingsRepository): IsGooglePayAvailableUseCase {
|
||||
return IsGooglePayAvailableUseCase(settingsRepository)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
1
common/google/.gitignore
vendored
Normal file
1
common/google/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
15
common/google/build.gradle.kts
Normal file
15
common/google/build.gradle.kts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.common.google"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation(deps.googlePlay.services.wallet)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tangem.google
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.gms.wallet.IsReadyToPayRequest
|
||||
import com.google.android.gms.wallet.PaymentsClient
|
||||
import com.google.android.gms.wallet.Wallet
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object GoogleServicesHelper {
|
||||
|
||||
private val allowedCardNetworks = JSONArray(
|
||||
listOf(
|
||||
"AMEX",
|
||||
"DISCOVER",
|
||||
"INTERAC",
|
||||
"JCB",
|
||||
"MASTERCARD",
|
||||
"VISA",
|
||||
),
|
||||
)
|
||||
|
||||
private val allowedCardAuthMethods = JSONArray(
|
||||
listOf(
|
||||
"PAN_ONLY",
|
||||
"CRYPTOGRAM_3DS",
|
||||
),
|
||||
)
|
||||
|
||||
private val baseCardPaymentMethod: JSONObject = JSONObject().apply {
|
||||
val parameters = JSONObject().apply {
|
||||
put("allowedAuthMethods", allowedCardAuthMethods)
|
||||
put("allowedCardNetworks", allowedCardNetworks)
|
||||
put("billingAddressRequired", true)
|
||||
put(
|
||||
"billingAddressParameters",
|
||||
JSONObject().apply {
|
||||
put("format", "FULL")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
put("type", "CARD")
|
||||
put("parameters", parameters)
|
||||
}
|
||||
|
||||
private val baseRequest = JSONObject().apply {
|
||||
put("apiVersion", 2)
|
||||
put("apiVersionMinor", 0)
|
||||
}
|
||||
|
||||
private val availabilityRequest = baseRequest.apply {
|
||||
put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod))
|
||||
}
|
||||
|
||||
fun createPaymentsClient(context: Context): PaymentsClient {
|
||||
val walletOptions = Wallet.WalletOptions.Builder()
|
||||
.build()
|
||||
return Wallet.getPaymentsClient(context, walletOptions)
|
||||
}
|
||||
|
||||
fun checkGoogleServicesAvailability(context: Context): Boolean {
|
||||
val googleApiAvailability = GoogleApiAvailability.getInstance()
|
||||
val status = googleApiAvailability.isGooglePlayServicesAvailable(context)
|
||||
|
||||
return status == ConnectionResult.SUCCESS
|
||||
}
|
||||
|
||||
suspend fun checkGooglePayAvailability(paymentsClient: PaymentsClient): Boolean {
|
||||
val request = IsReadyToPayRequest.fromJson(availabilityRequest.toString())
|
||||
val task = paymentsClient.isReadyToPay(request)
|
||||
|
||||
return suspendCoroutine<Result<Boolean>> { continuation ->
|
||||
task.addOnCompleteListener { completedTask ->
|
||||
try {
|
||||
val result = completedTask.getResult(ApiException::class.java)
|
||||
continuation.resume(Result.success(result))
|
||||
} catch (exception: ApiException) {
|
||||
continuation.resume(Result.failure(exception))
|
||||
}
|
||||
}
|
||||
}.getOrElse { false }
|
||||
}
|
||||
}
|
||||
|
|
@ -107,6 +107,10 @@ object PreferencesKeys {
|
|||
|
||||
val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") }
|
||||
|
||||
val IS_GOOGLE_SERVICES_AVAILABLE_KEY by lazy { booleanPreferencesKey(name = "isGoogleServicesAvailable") }
|
||||
|
||||
val IS_GOOGLE_PAY_AVAILABLE_KEY by lazy { booleanPreferencesKey(name = "isGooglePayAvailable") }
|
||||
|
||||
// region Permission
|
||||
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.onramp.converters
|
|||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.PaymentMethodType
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class PaymentMethodConverter : TwoWayConverter<PaymentMethodDTO, OnrampPaymentMethod> {
|
||||
|
|
@ -9,6 +10,7 @@ internal class PaymentMethodConverter : TwoWayConverter<PaymentMethodDTO, Onramp
|
|||
id = value.id,
|
||||
name = value.name,
|
||||
imageUrl = value.image,
|
||||
type = PaymentMethodType.getType(value.id),
|
||||
)
|
||||
|
||||
override fun convertBack(value: OnrampPaymentMethod): PaymentMethodDTO = PaymentMethodDTO(
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.withContext
|
|||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
internal class DefaultSettingsRepository(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val appLogsStore: AppLogsStore,
|
||||
|
|
@ -147,4 +148,26 @@ internal class DefaultSettingsRepository(
|
|||
userCountryFlow.value = code
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setGoogleServicesAvailability(value: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.IS_GOOGLE_SERVICES_AVAILABLE_KEY, value = value)
|
||||
}
|
||||
|
||||
override suspend fun isGoogleServicesAvailability(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.IS_GOOGLE_SERVICES_AVAILABLE_KEY,
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun setGooglePayAvailability(value: Boolean) {
|
||||
appPreferencesStore.store(key = PreferencesKeys.IS_GOOGLE_PAY_AVAILABLE_KEY, value = value)
|
||||
}
|
||||
|
||||
override suspend fun isGooglePayAvailability(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.IS_GOOGLE_PAY_AVAILABLE_KEY,
|
||||
default = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,5 +13,6 @@ dependencies {
|
|||
api(projects.domain.wallets.models)
|
||||
|
||||
api(projects.domain.core)
|
||||
api(projects.domain.settings)
|
||||
implementation(deps.kotlin.serialization)
|
||||
}
|
||||
|
|
@ -7,4 +7,34 @@ data class OnrampPaymentMethod(
|
|||
val id: String,
|
||||
val name: String,
|
||||
val imageUrl: String,
|
||||
val type: PaymentMethodType,
|
||||
)
|
||||
|
||||
enum class PaymentMethodType(val id: String?) {
|
||||
GOOGLE_PAY(id = "google-pay"),
|
||||
CARD(id = "card"),
|
||||
OTHER(id = null),
|
||||
;
|
||||
|
||||
fun getPriority(isGooglePayEnabled: Boolean): Int = if (isGooglePayEnabled) {
|
||||
when (this) {
|
||||
GOOGLE_PAY -> 0
|
||||
CARD -> 1
|
||||
OTHER -> 2
|
||||
}
|
||||
} else {
|
||||
when (this) {
|
||||
CARD -> 0
|
||||
GOOGLE_PAY -> 1
|
||||
OTHER -> 2
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getType(id: String): PaymentMethodType = when (id) {
|
||||
GOOGLE_PAY.id -> GOOGLE_PAY
|
||||
CARD.id -> CARD
|
||||
else -> OTHER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,15 +5,34 @@ import arrow.core.left
|
|||
import arrow.core.right
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import java.math.BigDecimal
|
||||
|
||||
class GetOnrampQuotesUseCase(private val repository: OnrampRepository) {
|
||||
class GetOnrampQuotesUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val repository: OnrampRepository,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<Either<Throwable, List<OnrampQuote>>> {
|
||||
return repository.getQuotes()
|
||||
.map<List<OnrampQuote>, Either<Throwable, List<OnrampQuote>>> { it.right() }
|
||||
.map<List<OnrampQuote>, Either<Throwable, List<OnrampQuote>>> { quotes ->
|
||||
val isGooglePayAvailable = settingsRepository.isGooglePayAvailability()
|
||||
|
||||
quotes.groupBy { it.paymentMethod.type }
|
||||
.asSequence()
|
||||
.sortedBy { it.key.getPriority(isGooglePayAvailable) }
|
||||
.map { grouped ->
|
||||
grouped.value.sortedByDescending {
|
||||
(it as? OnrampQuote.Data)?.toAmount?.value ?: BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
.toList()
|
||||
.flatten()
|
||||
.right()
|
||||
}
|
||||
.catch { emit(it.left()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
||||
class IsGooglePayAvailableUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke() = Either.catch {
|
||||
settingsRepository.isGooglePayAvailability()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
||||
class IsGoogleServicesAvailableUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke() = Either.catch {
|
||||
settingsRepository.isGoogleServicesAvailability()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
||||
class SetGooglePayAvailabilityUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(isAvailable: Boolean) = Either.catch {
|
||||
settingsRepository.setGooglePayAvailability(isAvailable)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.domain.settings
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
|
||||
class SetGoogleServicesAvailabilityUseCase(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(isAvailable: Boolean) = Either.catch {
|
||||
settingsRepository.setGoogleServicesAvailability(isAvailable)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.settings.repositories
|
|||
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface SettingsRepository {
|
||||
|
||||
suspend fun shouldShowSaveUserWalletScreen(): Boolean
|
||||
|
|
@ -39,4 +40,12 @@ interface SettingsRepository {
|
|||
suspend fun getUserCountryCodeSync(): UserCountry?
|
||||
|
||||
suspend fun fetchUserCountryCode()
|
||||
|
||||
suspend fun setGoogleServicesAvailability(value: Boolean)
|
||||
|
||||
suspend fun isGoogleServicesAvailability(): Boolean
|
||||
|
||||
suspend fun setGooglePayAvailability(value: Boolean)
|
||||
|
||||
suspend fun isGooglePayAvailability(): Boolean
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ dependencies {
|
|||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -122,7 +123,9 @@ internal class SelectProviderModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun List<OnrampProviderWithQuote>.toProvidersListItems() = mapIndexed { index, quote ->
|
||||
private fun List<OnrampProviderWithQuote>.toProvidersListItems() = sortedByDescending {
|
||||
(it as? OnrampProviderWithQuote.Data)?.toAmount?.value ?: BigDecimal.ZERO
|
||||
}.mapIndexed { index, quote ->
|
||||
when (quote) {
|
||||
is OnrampProviderWithQuote.Data -> {
|
||||
val rate = quote.toAmount.value.format {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ include(":core:pagination")
|
|||
|
||||
// region Common modules
|
||||
include(":common:ui")
|
||||
include(":common:google")
|
||||
// endregion
|
||||
|
||||
// region Libs modules
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue