From f2dfe2f8bcc42dd2c917e852d6395d0e9fe8842b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 3 Dec 2024 15:43:15 +0500 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../main/java/com/tangem/tap/MainActivity.kt | 30 +++++++ .../tap/di/domain/OnrampDomainModule.kt | 11 ++- .../tap/di/domain/SettingsDomainModule.kt | 32 +++++++ common/google/.gitignore | 1 + common/google/build.gradle.kts | 15 ++++ .../com/tangem/google/GoogleServicesHelper.kt | 89 +++++++++++++++++++ .../local/preferences/PreferencesKeys.kt | 4 + .../converters/PaymentMethodConverter.kt | 2 + .../settings/DefaultSettingsRepository.kt | 23 +++++ domain/onramp/build.gradle.kts | 1 + .../onramp/model/OnrampPaymentMethod.kt | 32 ++++++- .../domain/onramp/GetOnrampQuotesUseCase.kt | 23 ++++- .../settings/IsGooglePayAvailableUseCase.kt | 13 +++ .../IsGoogleServicesAvailableUseCase.kt | 13 +++ .../SetGooglePayAvailabilityUseCase.kt | 13 +++ .../SetGoogleServicesAvailabilityUseCase.kt | 13 +++ .../repositories/SettingsRepository.kt | 9 ++ features/onramp/impl/build.gradle.kts | 1 + .../providers/model/SelectProviderModel.kt | 5 +- settings.gradle.kts | 1 + 21 files changed, 326 insertions(+), 6 deletions(-) create mode 100644 common/google/.gitignore create mode 100644 common/google/build.gradle.kts create mode 100644 common/google/src/main/java/com/tangem/google/GoogleServicesHelper.kt create mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/IsGooglePayAvailableUseCase.kt create mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/IsGoogleServicesAvailableUseCase.kt create mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/SetGooglePayAvailabilityUseCase.kt create mode 100644 domain/settings/src/main/java/com/tangem/domain/settings/SetGoogleServicesAvailabilityUseCase.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c939548392..d63ff6b227 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 61af04c644..25ec1c6a48 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -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 @@ -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 } diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt index db4247ca10..3204990b62 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 9d42683053..352171f53a 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -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 } \ No newline at end of file diff --git a/common/google/.gitignore b/common/google/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/common/google/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/google/build.gradle.kts b/common/google/build.gradle.kts new file mode 100644 index 0000000000..f66ce5bef8 --- /dev/null +++ b/common/google/build.gradle.kts @@ -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) + +} \ No newline at end of file diff --git a/common/google/src/main/java/com/tangem/google/GoogleServicesHelper.kt b/common/google/src/main/java/com/tangem/google/GoogleServicesHelper.kt new file mode 100644 index 0000000000..e81718555d --- /dev/null +++ b/common/google/src/main/java/com/tangem/google/GoogleServicesHelper.kt @@ -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> { 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 } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index cf9ecaf3f0..488c466107 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -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") diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/PaymentMethodConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/PaymentMethodConverter.kt index f29bfe88ae..6371ca03bd 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/converters/PaymentMethodConverter.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/PaymentMethodConverter.kt @@ -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 { @@ -9,6 +10,7 @@ internal class PaymentMethodConverter : TwoWayConverter 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 + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt index 2cc55a2f46..bc37384338 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampQuotesUseCase.kt @@ -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>> { return repository.getQuotes() - .map, Either>> { it.right() } + .map, Either>> { 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()) } } } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/IsGooglePayAvailableUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/IsGooglePayAvailableUseCase.kt new file mode 100644 index 0000000000..e3858b5424 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/IsGooglePayAvailableUseCase.kt @@ -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() + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/IsGoogleServicesAvailableUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/IsGoogleServicesAvailableUseCase.kt new file mode 100644 index 0000000000..034be474de --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/IsGoogleServicesAvailableUseCase.kt @@ -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() + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/SetGooglePayAvailabilityUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/SetGooglePayAvailabilityUseCase.kt new file mode 100644 index 0000000000..36f04ae0ad --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/SetGooglePayAvailabilityUseCase.kt @@ -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) + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/SetGoogleServicesAvailabilityUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/SetGoogleServicesAvailabilityUseCase.kt new file mode 100644 index 0000000000..3052dbfef0 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/SetGoogleServicesAvailabilityUseCase.kt @@ -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) + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index b342dcd2ef..22f8fec52b 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -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 } \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 90f319d272..ea3b825ef0 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -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) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt index a4a558cf10..753d8ab225 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt @@ -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.toProvidersListItems() = mapIndexed { index, quote -> + private fun List.toProvidersListItems() = sortedByDescending { + (it as? OnrampProviderWithQuote.Data)?.toAmount?.value ?: BigDecimal.ZERO + }.mapIndexed { index, quote -> when (quote) { is OnrampProviderWithQuote.Data -> { val rate = quote.toAmount.value.format { diff --git a/settings.gradle.kts b/settings.gradle.kts index d8139ac1f0..1db213040e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -140,6 +140,7 @@ include(":core:pagination") // region Common modules include(":common:ui") +include(":common:google") // endregion // region Libs modules