diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ce615a8e11..0ca24d28ab 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -183,10 +183,6 @@ dependencies { implementation(deps.kotsonGson) implementation(deps.spongecastle.core) implementation(deps.lottie) - implementation(deps.shopify.buy) { - exclude(group = "com.shopify.graphql.support") - exclude(module = "joda-time") - } implementation(deps.compose.accompanist.appCompatTheme) implementation(deps.compose.accompanist.systemUiController) implementation(deps.compose.accompanist.webView) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 116e6dcf12..99a374a21f 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -52,9 +52,6 @@ import com.tangem.tap.common.OnActivityResultCallback import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder import com.tangem.tap.common.redux.NotificationsHandler -import com.tangem.tap.common.shop.googlepay.GooglePayService -import com.tangem.tap.common.shop.googlepay.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE -import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor @@ -64,7 +61,6 @@ import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHan import com.tangem.tap.features.main.MainViewModel import com.tangem.tap.features.main.model.Toast import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction -import com.tangem.tap.features.shop.redux.ShopAction import com.tangem.tap.features.welcome.ui.WelcomeFragment import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.proxy.redux.DaggerGraphAction @@ -173,8 +169,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac setContentView(R.layout.activity_main) initContent() - checkGooglePayAvailability() - checkForNotificationPermission() observeStateUpdates() @@ -256,14 +250,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } - private fun checkGooglePayAvailability() { - store.dispatch( - ShopAction.CheckIfGooglePayAvailable( - GooglePayService(createPaymentsClient(this), this), - ), - ) - } - private fun createAppThemeModeFlow(): SharedFlow { val tapApplication = application as TapApplication @@ -377,13 +363,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac super.onActivityResult(requestCode, resultCode, data) onActivityResultCallbacks.forEach { it(requestCode, resultCode, data) } - when (requestCode) { - LOAD_PAYMENT_DATA_REQUEST_CODE -> { - store.dispatch( - ShopAction.BuyWithGooglePay.HandleGooglePayResponse(resultCode, data), - ) - } - } } override fun addOnActivityResultCallback(callback: OnActivityResultCallback) { @@ -432,10 +411,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 val isNotScannedBefore = store.state.globalState.scanResponse == null val isOnboardingServiceNotActive = store.state.globalState.onboardingState.onboardingStarted - val isShopNotOpened = store.state.shopState.total != null when { - !backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive && isShopNotOpened -> { + !backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive -> { navigateToInitialScreen(intentWhichStartedActivity) } backStackIsEmpty -> { diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index de7e20d595..7c26fe7570 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -59,7 +59,6 @@ import com.tangem.tap.common.log.TimberFormatStrategy import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.appReducer import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.common.shop.TangemShopService import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tasks.product.DerivationsFinder import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation @@ -84,7 +83,6 @@ lateinit var foregroundActivityObserver: ForegroundActivityObserver lateinit var activityResultCaller: ActivityResultCaller lateinit var preferencesStorage: PreferencesDataSource lateinit var walletConnectRepository: WalletConnectRepository -lateinit var shopService: TangemShopService internal lateinit var derivationsFinder: DerivationsFinder @HiltAndroidApp @@ -300,7 +298,6 @@ internal class TapApplication : Application(), ImageLoaderFactory { } private fun initWithConfigDependency(config: Config) { - shopService = TangemShopService(this, config.shopify!!) initAnalytics(this, config) initFeedbackManager(this, foregroundActivityObserver, store) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt deleted file mode 100644 index fd658029b7..0000000000 --- a/app/src/main/java/com/tangem/tap/common/analytics/converters/ShopOrderToEventConverter.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.tap.common.analytics.converters - -import com.shopify.buy3.Storefront -import com.tangem.tap.common.analytics.events.Shop -import com.tangem.tap.features.shop.domain.models.ProductType -import com.tangem.utils.converter.Converter - -/** -[REDACTED_AUTHOR] - */ -class ShopOrderToEventConverter : Converter, Shop.Purchased> { - - override fun convert(value: Pair): Shop.Purchased { - val checkout = value.first - val productType = value.second - - val sku = checkout.lineItems?.edges?.firstOrNull()?.node?.variant?.sku ?: productType.sku - val count = when (productType) { - ProductType.WALLET_2_CARDS -> "2" - ProductType.WALLET_3_CARDS -> "3" - } - val amount = "${checkout.totalPriceV2.amount} ${checkout.totalPriceV2.currencyCode.name}" - val code = (checkout.discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication) - ?.code - - return Shop.Purchased(sku, count, amount, code) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt index 019b203988..f6c299acb1 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt @@ -24,7 +24,6 @@ import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragm import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment import com.tangem.tap.features.send.ui.SendFragment -import com.tangem.tap.features.shop.ui.ShopFragment import com.tangem.tap.features.tokens.impl.presentation.TokensListFragment import com.tangem.tap.features.welcome.ui.WelcomeFragment import com.tangem.tap.proxy.redux.DaggerGraphState @@ -122,7 +121,6 @@ fun FragmentActivity.getPreviousScreen(): AppScreen? { private fun fragmentFactory(screen: AppScreen): Fragment { return when (screen) { AppScreen.Home -> HomeFragment() - AppScreen.Shop -> ShopFragment() AppScreen.OnboardingNote -> OnboardingNoteFragment() AppScreen.OnboardingWallet -> OnboardingWalletFragment() AppScreen.OnboardingTwins -> OnboardingTwinsFragment() diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index a1232dd031..cd51cff94e 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -12,7 +12,6 @@ import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer -import com.tangem.tap.features.shop.redux.ShopReducer import com.tangem.tap.features.signin.redux.SignInReducer import com.tangem.tap.features.tokens.legacy.redux.TokensReducer import com.tangem.tap.features.welcome.redux.WelcomeReducer @@ -37,7 +36,6 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder) disclaimerState = DisclaimerReducer.reduce(action, state), tokensState = TokensReducer.reduce(action, state), walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState), - shopState = ShopReducer.reduce(action, state.shopState), welcomeState = WelcomeReducer.reduce(action, state), saveWalletState = SaveWalletReducer.reduce(action, state), signInState = SignInReducer.reduce(action, state), diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index 892271058e..7cab6dbfbd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -29,8 +29,6 @@ import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware import com.tangem.tap.features.saveWallet.redux.SaveWalletState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware import com.tangem.tap.features.send.redux.states.SendState -import com.tangem.tap.features.shop.redux.ShopMiddleware -import com.tangem.tap.features.shop.redux.ShopState import com.tangem.tap.features.signin.redux.SignInMiddleware import com.tangem.tap.features.signin.redux.SignInState import com.tangem.tap.features.tokens.legacy.redux.TokensState @@ -56,7 +54,6 @@ data class AppState( val disclaimerState: DisclaimerState = DisclaimerState(), val tokensState: TokensState = TokensState(), val walletConnectState: WalletConnectState = WalletConnectState(), - val shopState: ShopState = ShopState(), val welcomeState: WelcomeState = WelcomeState(), val saveWalletState: SaveWalletState = SaveWalletState(), val signInState: SignInState = SignInState(), @@ -92,7 +89,6 @@ data class AppState( DisclaimerMiddleware().disclaimerMiddleware, WalletConnectMiddleware().walletConnectMiddleware, BackupMiddleware().backupMiddleware, - ShopMiddleware().shopMiddleware, WelcomeMiddleware().middleware, SaveWalletMiddleware().middleware, LockUserWalletsTimerMiddleware().middleware, diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt deleted file mode 100644 index 1a1b5827f0..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt +++ /dev/null @@ -1,222 +0,0 @@ -package com.tangem.tap.common.shop - -import android.app.Application -import android.content.Intent -import com.google.android.gms.wallet.PaymentData -import com.shopify.buy3.Storefront -import com.tangem.core.analytics.Analytics -import com.tangem.datasource.config.models.ShopifyShop -import com.tangem.tap.common.analytics.converters.ShopOrderToEventConverter -import com.tangem.tap.common.extensions.filterNotNull -import com.tangem.tap.common.shop.data.TangemProduct -import com.tangem.tap.common.shop.data.TotalSum -import com.tangem.tap.common.shop.googlepay.GooglePayService -import com.tangem.tap.common.shop.shopify.ShopifyService -import com.tangem.tap.common.shop.shopify.data.CheckoutItem -import com.tangem.tap.features.shop.domain.models.ProductType -import com.tangem.tap.features.shop.domain.models.ProductType.Companion.SKUS_TO_DISPLAY -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import java.math.BigDecimal -import java.util.Currency -import java.util.UUID - -class TangemShopService(application: Application, shopifyShop: ShopifyShop) { - - private val shopifyService = ShopifyService(application, shopifyShop) - - private val checkouts = mutableMapOf() - private val variants = mutableMapOf() - - private lateinit var googlePayService: GooglePayService - - suspend fun getProducts(): Result> { - val result = shopifyService.getProducts() - - return result.mapCatching { product -> - val availableVariants = product - .flatMap { it.variants.edges.map { it.node } } - .filter { SKUS_TO_DISPLAY.contains(it.sku) } - .associateBy { ProductType.fromSku(it.sku) } - .filterNotNull() - variants.putAll(availableVariants) - - if (variants.size < SKUS_TO_DISPLAY.size) { - return Result.failure( - Exception( - "Shopify: products are missing, " + - "\nproducts available: ${variants.keys.map { it.sku }}", - ), - ) - } - - val twoCardsProduct = TangemProduct( - type = ProductType.WALLET_2_CARDS, - totalSum = TotalSum( - finalValue = variants[ProductType.WALLET_2_CARDS]?.priceV2?.format(), - beforeDiscount = variants[ProductType.WALLET_2_CARDS]?.compareAtPriceV2?.format(), - ), - ) - val threeCardsProduct = TangemProduct( - type = ProductType.WALLET_3_CARDS, - totalSum = TotalSum( - finalValue = variants[ProductType.WALLET_3_CARDS]?.priceV2?.format(), - beforeDiscount = variants[ProductType.WALLET_3_CARDS]?.compareAtPriceV2?.format(), - ), - ) - createCheckouts() - return Result.success(listOf(twoCardsProduct, threeCardsProduct)) - } - } - - private suspend fun createCheckouts() { - variants.keys.map { coroutineScope { async { createCheckout(it) } } }.awaitAll() - } - - private suspend fun createCheckout(productType: ProductType) { - val checkoutItem = CheckoutItem(variants[productType]!!.id, 1) - val result = shopifyService.createCheckout(listOf(checkoutItem)) - result.onSuccess { checkout -> - checkouts[productType] = checkout - } - } - - suspend fun checkIfGooglePayAvailable(googlePayService: GooglePayService): Result { - this.googlePayService = googlePayService - return googlePayService.checkIfGooglePayAvailable(shopifyService.shop.merchantID != null) - } - - fun buyWithGooglePay(productType: ProductType) { - shopifyService.shop.merchantID?.let { merchantId -> - val totalPrice = checkouts[productType]!!.totalPriceV2.amount - googlePayService.payWithGooglePay( - totalPriceCents = totalPrice, - currencyCode = checkouts[productType]!!.currencyCode.name, - merchantID = merchantId, - ) - } - } - -// fun subscribeToGooglePayResult( -// productType: ProductType, -// resultCallback: (Result) -> Unit -// ) { -// googlePayService.responseCallback = { result -> -// result.onFailure { } -// result.onSuccess { -// completeTokenizedPayment(it, productType) -// } -// } -// } - - suspend fun handleGooglePayResult(resultCode: Int, data: Intent?, productType: ProductType): Result { - val result = googlePayService.handleResponseFromGooglePay(resultCode, data) - result.onSuccess { - val finalizePaymentResult = completeTokenizedPayment(it, productType) - finalizePaymentResult.onSuccess { - return Result.success(Unit) - } - return Result.failure(finalizePaymentResult.exceptionOrNull()!!) - } - return Result.failure(result.exceptionOrNull()!!) - } - - private suspend fun completeTokenizedPayment( - paymentData: PaymentData, - productType: ProductType, - ): Result { - val checkout = checkouts[productType]!! - val googlePayResponse = - googlePayService.parsePaymentData(paymentData) - ?: return Result.failure(Exception("cannot parse GPay result")) - - val amount = - Storefront.MoneyInput(checkout.totalPriceV2.amount, checkout.totalPriceV2.currencyCode) - val idempotencyKey = UUID.randomUUID().toString() - val addressGPay = googlePayResponse.billingAddress - - val address = Storefront.MailingAddressInput().apply { - lastName = addressGPay.name - address1 = addressGPay.address1 - address2 = addressGPay.address2 + addressGPay.address3 - province = addressGPay.administrativeArea - zip = addressGPay.postalCode - phone = addressGPay.phoneNumber - } - - val payment = Storefront.TokenizedPaymentInputV3( - amount, - idempotencyKey, - address, - paymentData.toJson(), - Storefront.PaymentTokenType.GOOGLE_PAY, - ) - .setTest(true) - - return shopifyService.completeWithTokenizedPayment( - payment = payment, - checkoutID = checkout.id, - ) - } - - suspend fun applyPromoCode(promoCode: String): Result> { - val products = variants.keys - .map { coroutineScope { async { applyPromoCode(promoCode, it) } } } - .awaitAll() - .map { result -> result.getOrElse { return Result.failure(it) } } - - return Result.success(products) - } - - private suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result { - val checkout = checkouts[productType] ?: return Result.failure(Exception("No checkout")) - - val result = if (promoCode.isBlank()) { - shopifyService.removeDiscount(checkout.id) - } else { - shopifyService.applyDiscount(promoCode, checkout.id) - } - - result.onSuccess { - checkouts[productType] = it - return Result.success( - TangemProduct( - productType, - TotalSum( - finalValue = it.totalPriceV2.format(), - beforeDiscount = variants[productType]!!.compareAtPriceV2.format(), - ), - appliedDiscount = it.getAppliedDiscount(), - ), - ) - } - return Result.failure(result.exceptionOrNull()!!) - } - - fun getCheckoutUrl(productType: ProductType): String { - return checkouts[productType]!!.webUrl - } - - suspend fun waitForCheckout(productType: ProductType) { - val result = shopifyService.checkout(true, checkouts[productType]!!.id) - result.onSuccess { checkout -> - if (checkout.order != null && checkout.lineItems != null) { - val event = ShopOrderToEventConverter().convert(checkout to productType) - Analytics.send(event) - } - } - } -} - -private fun Storefront.MoneyV2.format(): String { - val currencySymbol = Currency.getInstance(currencyCode.name).symbol - val amountFormatted = BigDecimal(amount).setScale(2) - return currencySymbol + amountFormatted -} - -private fun Storefront.Checkout.getAppliedDiscount(): String? { - val discountApplication = - discountApplications.edges.firstOrNull()?.node as? Storefront.DiscountCodeApplication - return discountApplication?.code -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/data/TangemProduct.kt b/app/src/main/java/com/tangem/tap/common/shop/data/TangemProduct.kt deleted file mode 100644 index 9a01a5df51..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/data/TangemProduct.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.tap.common.shop.data - -import com.tangem.tap.features.shop.domain.models.ProductType - -data class TangemProduct( - val type: ProductType, - val totalSum: TotalSum? = null, - val appliedDiscount: String? = null, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/data/TotalSum.kt b/app/src/main/java/com/tangem/tap/common/shop/data/TotalSum.kt deleted file mode 100644 index a9e920745f..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/data/TotalSum.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.tap.common.shop.data - -data class TotalSum( - val finalValue: String? = null, - val beforeDiscount: String? = null, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt deleted file mode 100644 index 7526224414..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayService.kt +++ /dev/null @@ -1,147 +0,0 @@ -package com.tangem.tap.common.shop.googlepay - -import android.app.Activity -import android.app.Activity.RESULT_CANCELED -import android.app.Activity.RESULT_OK -import android.content.Intent -import android.util.Log -import com.google.android.gms.common.api.ApiException -import com.google.android.gms.wallet.* -import com.tangem.common.core.TangemSdkError -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.json.JSONException -import org.json.JSONObject -import timber.log.Timber -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -class GooglePayService(private val paymentsClient: PaymentsClient, private val activity: Activity) { - -// var responseCallback: ((Result) -> Unit)? = null - - suspend fun checkIfGooglePayAvailable(isMerchantAvailable: Boolean): Result { - if (!isMerchantAvailable) return Result.success(false) - val isReadyToPayJson = GooglePayUtil.isReadyToPayRequest() ?: return Result.success(false) - val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString()) - - val task = paymentsClient.isReadyToPay(request) - return withContext(Dispatchers.IO) { - suspendCoroutine { continuation -> - task.addOnCompleteListener { completedTask -> - try { - val result = completedTask.getResult(ApiException::class.java) - continuation.resume(Result.success(true)) - } catch (exception: ApiException) { - // Process error - Timber.w("isReadyToPay failed: $exception") - continuation.resume(Result.failure(exception)) - } - } - } - } - } - - fun payWithGooglePay(totalPriceCents: String, currencyCode: String, merchantID: String) { - val paymentDataRequestJson = GooglePayUtil.getPaymentDataRequest( - totalPriceCents, - currencyCode = currencyCode, - countryCode = "RU", - merchantID = merchantID, - ) - if (paymentDataRequestJson == null) { - Timber.e("RequestPayment: can't fetch payment data request") - return - } - val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString()) - - AutoResolveHelper.resolveTask( - paymentsClient.loadPaymentData(request), - activity, - LOAD_PAYMENT_DATA_REQUEST_CODE, - ) - } - - fun handleResponseFromGooglePay(resultCode: Int, data: Intent?): Result { - val result = when (resultCode) { - RESULT_OK -> { - val paymentData = data?.let { intent -> PaymentData.getFromIntent(intent) } - if (paymentData == null) { - Result.failure(Exception("No payment data")) - } else { - Result.success(paymentData) - } - } - RESULT_CANCELED -> { - Result.failure(TangemSdkError.UserCancelled()) - } - AutoResolveHelper.RESULT_ERROR -> { - val statusCode = AutoResolveHelper.getStatusFromIntent(data)?.statusCode - if (statusCode == null) { - Result.failure(Exception("Unknown Status")) - } else { - Result.failure(Exception("$statusCode")) - } - } - else -> Result.failure(Exception("Unknown Status")) - } -// responseCallback?.invoke(result) - return result - } - - fun parsePaymentData(paymentData: PaymentData): GooglePayResponse? { - val paymentInformation = paymentData.toJson() - - try { - // Token will be null if PaymentDataRequest was not constructed using fromJson(String). - val paymentMethodData = - JSONObject(paymentInformation).getJSONObject("paymentMethodData") - val addressJson = paymentMethodData.getJSONObject("info") - .getJSONObject("billingAddress") - - val address = Address( - name = addressJson.getString("name"), - postalCode = addressJson.getString("postalCode"), - countryCode = addressJson.getString("countryCode"), - phoneNumber = addressJson.getString("phoneNumber"), - address1 = addressJson.getString("address1"), - address2 = addressJson.getString("address2"), - address3 = addressJson.getString("address3"), - locality = addressJson.getString("locality"), - administrativeArea = addressJson.getString("administrativeArea"), - sortingCode = addressJson.getString("sortingCode"), - ) - - val token = paymentMethodData - .getJSONObject("tokenizationData") - .getString("token") - - return GooglePayResponse(address, token) - } catch (e: JSONException) { - Log.e("handlePaymentSuccess", "Error: " + e.toString()) - } - return null - } - - companion object { - const val LOAD_PAYMENT_DATA_REQUEST_CODE = 315 - } -} - -data class GooglePayResponse( - val billingAddress: Address, - val token: String, -) - -data class Address( - val name: String, - val postalCode: String, - val countryCode: String, - val phoneNumber: String, - val address1: String, - val address2: String, - val address3: String, - val locality: String, - val administrativeArea: String, - val sortingCode: String, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt b/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt deleted file mode 100644 index 1e5cfba714..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/googlepay/GooglePayUtil.kt +++ /dev/null @@ -1,133 +0,0 @@ -package com.tangem.tap.common.shop.googlepay - -import android.app.Activity -import com.google.android.gms.wallet.PaymentsClient -import com.google.android.gms.wallet.Wallet -import com.google.android.gms.wallet.WalletConstants -import org.json.JSONArray -import org.json.JSONException -import org.json.JSONObject - -object GooglePayUtil { - private val baseRequest = JSONObject().apply { - put("apiVersion", 2) - put("apiVersionMinor", 0) - } - - @Suppress("MagicNumber") - private val allowedCardNetworks = JSONArray( - listOf( - "AMEX", - "DISCOVER", - "INTERAC", - "JCB", - "MASTERCARD", - "VISA", - ), - ) - - private val allowedCardAuthMethods = JSONArray( - listOf( - "PAN_ONLY", - "CRYPTOGRAM_3DS", - ), - ) - - private val merchantInfo: JSONObject = JSONObject().put("merchantName", "Example Merchant") - - private fun gatewayTokenizationSpecification(merchantID: String): JSONObject { - return JSONObject().apply { - put("type", "PAYMENT_GATEWAY") - put( - "parameters", - JSONObject( - mapOf( - "gateway" to "shopify", - "gatewayMerchantId" to merchantID, - ), - ), - ) - } - } - - private fun baseCardPaymentMethod(): JSONObject { - return 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 fun cardPaymentMethod(merchantID: String): JSONObject { - val cardPaymentMethod = baseCardPaymentMethod() - cardPaymentMethod.put("tokenizationSpecification", gatewayTokenizationSpecification(merchantID)) - - return cardPaymentMethod - } - - fun createPaymentsClient(activity: Activity): PaymentsClient { - val walletOptions = Wallet.WalletOptions.Builder() - .setEnvironment(PAYMENTS_ENVIRONMENT) - .build() - - return Wallet.getPaymentsClient(activity, walletOptions) - } - - fun isReadyToPayRequest(): JSONObject? { - return try { - baseRequest.apply { - put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod())) - } - } catch (e: JSONException) { - null - } - } - - private fun getTransactionInfo(price: String, countryCode: String, currencyCode: String): JSONObject { - return JSONObject().apply { - put("totalPrice", price) - put("totalPriceStatus", "FINAL") - put("countryCode", countryCode) - put("currencyCode", currencyCode) - } - } - - fun getPaymentDataRequest( - price: String, - countryCode: String, - currencyCode: String, - merchantID: String, - ): JSONObject? { - try { - return baseRequest.apply { - put("allowedPaymentMethods", JSONArray().put(cardPaymentMethod(merchantID))) - put("transactionInfo", getTransactionInfo(price, countryCode, currencyCode)) - put("merchantInfo", merchantInfo) - - // An optional shipping address requirement is a top-level property of the - // PaymentDataRequest JSON object. - val shippingAddressParameters = JSONObject().apply { - put("phoneNumberRequired", false) -// put("allowedCountryCodes", JSONArray(listOf("US", "GB"))) - } - put("shippingAddressParameters", shippingAddressParameters) - put("shippingAddressRequired", true) - } - } catch (e: JSONException) { - return null - } - } -} - -const val PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt deleted file mode 100644 index 705bd067a0..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/ShopifyService.kt +++ /dev/null @@ -1,267 +0,0 @@ -package com.tangem.tap.common.shop.shopify - -import android.app.Application -import com.shopify.buy3.GraphCallResult -import com.shopify.buy3.GraphClient -import com.shopify.buy3.RetryHandler -import com.shopify.buy3.Storefront.* -import com.shopify.graphql.support.ID -import com.shopify.graphql.support.Input -import com.tangem.datasource.config.models.ShopifyShop -import com.tangem.tap.common.shop.shopify.data.CheckoutItem -import com.tangem.tap.common.shop.shopify.data.checkoutFieldsFragment -import com.tangem.tap.common.shop.shopify.data.collectionFieldsFragment -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.util.concurrent.TimeUnit -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -@Suppress("LargeClass") -class ShopifyService(private val application: Application, val shop: ShopifyShop) { - private val client: GraphClient by lazy { initClient() } - - @Suppress("MagicNumber") - suspend fun getProducts(collectionTitleFilter: String? = null): Result> { - val filter = collectionTitleFilter?.let { "title:\"$it\"" } - - val query = query { rootQuery: QueryRootQuery -> - rootQuery.collections( - { arg -> arg.first(250).query(filter) }, - CollectionConnectionQuery::collectionFieldsFragment, - ) - } - return when (val result = queryAsync(query)) { - is GraphCallResult.Success -> { - val products = result.response.data!!.collections.edges - .map { it.node.products } - .flatMap { it.edges } - .map { it.node } - Result.success(products) - } - is GraphCallResult.Failure -> { - Result.failure(result.error) - } - } - } - - suspend fun checkout(pollUntilOrder: Boolean, checkoutID: ID): Result { - val query = query { rootQuery: QueryRootQuery -> - rootQuery - .node(checkoutID) { query -> - query.onCheckout { checkoutQuery -> - with(checkoutQuery) { - checkoutFieldsFragment() - } - } - } - } - val retryHandler = RetryHandler.build( - delay = 1, - timeUnit = TimeUnit.SECONDS, - ) { - this.retryWhen { result -> - when (result) { - is GraphCallResult.Success -> { - val checkout = result.response.data?.node as? Checkout - checkout?.order == null - } - is GraphCallResult.Failure -> false - } - } - } - - val result = if (pollUntilOrder) queryAsync(query, retryHandler) else queryAsync(query) - return when (result) { - is GraphCallResult.Success -> { - val checkout = result.response.data!!.node as? Checkout - if (checkout != null) { - Result.success(checkout) - } else { - Result.failure(ShopifyError.Unknown) - } - } - is GraphCallResult.Failure -> { - Result.failure(result.error) - } - } - } - - suspend fun createCheckout(checkoutItems: List, checkoutID: ID? = null): Result { - val storefrontLineItems: MutableList = checkoutItems - .map { CheckoutLineItemInput(it.quantity, it.id) }.toMutableList() - - val query = if (checkoutID != null) { - mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutLineItemsReplace( - storefrontLineItems, - checkoutID, - ) { payloadQuery: CheckoutLineItemsReplacePayloadQuery -> - payloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .userErrors { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - } else { - val input = CheckoutCreateInput() - .setLineItemsInput( - Input.value(storefrontLineItems), - ) - mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutCreate( - input, - ) { payloadQuery: CheckoutCreatePayloadQuery -> - payloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - } - return runCheckoutMutation(query) - } - - suspend fun applyDiscount(discountCode: String, checkoutID: ID): Result { - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutDiscountCodeApplyV2( - discountCode, - checkoutID, - ) { payloadQuery: CheckoutDiscountCodeApplyV2PayloadQuery -> - payloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - return runCheckoutMutation(query) - } - - suspend fun removeDiscount(checkoutID: ID): Result { - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutDiscountCodeRemove( - checkoutID, - ) { payloadQuery: CheckoutDiscountCodeRemovePayloadQuery -> - payloadQuery - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery.checkoutFieldsFragment() - } - .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - return runCheckoutMutation(query) - } - - suspend fun completeWithTokenizedPayment(payment: TokenizedPaymentInputV3, checkoutID: ID): Result { - val query = mutation { mutationQuery: MutationQuery -> - mutationQuery - .checkoutCompleteWithTokenizedPaymentV3( - checkoutID, - payment, - ) { payloadQuery: CheckoutCompleteWithTokenizedPaymentV3PayloadQuery -> - payloadQuery - .payment { paymentQuery: PaymentQuery -> - paymentQuery - .ready() - .errorMessage() - } - .checkout { checkoutQuery: CheckoutQuery -> - checkoutQuery - .ready() - } - .checkoutUserErrors { userErrorQuery: CheckoutUserErrorQuery -> - userErrorQuery - .field() - .message() - } - } - } - return runCheckoutMutation(query) - } - - private suspend fun runCheckoutMutation(mutation: MutationQuery): Result { - return when (val result = mutationQueryAsync(mutation)) { - is GraphCallResult.Success -> { - val checkout = result.response.data!!.checkoutCreate?.checkout - ?: result.response.data!!.checkoutDiscountCodeApplyV2?.checkout - ?: result.response.data!!.checkoutDiscountCodeRemove?.checkout - ?: result.response.data!!.checkoutShippingAddressUpdateV2?.checkout - ?: result.response.data!!.checkoutEmailUpdateV2?.checkout - ?: result.response.data!!.checkoutShippingLineUpdate?.checkout - ?: result.response.data!!.checkoutCompleteWithTokenizedPaymentV3.checkout - - Result.success(checkout) - } - is GraphCallResult.Failure -> Result.failure(result.error) - } - } - - private suspend fun queryAsync( - query: QueryRootQuery, - retryHandler: RetryHandler, - ): GraphCallResult = withContext(Dispatchers.IO) { - suspendCoroutine { continuation -> - client.queryGraph(query).enqueue(retryHandler = retryHandler) { result -> - continuation.resume(result) - } - } - } - - private suspend fun queryAsync(query: QueryRootQuery): GraphCallResult = withContext(Dispatchers.IO) { - suspendCoroutine { continuation -> - client.queryGraph(query).enqueue { result -> - continuation.resume(result) - } - } - } - - private suspend fun mutationQueryAsync(query: MutationQuery): GraphCallResult = - withContext(Dispatchers.IO) { - suspendCoroutine { continuation -> - client.mutateGraph(query).enqueue { result -> - continuation.resume(result) - } - } - } - - private fun initClient(): GraphClient { - return GraphClient.build( - application, - shop.domain, - shop.storefrontApiKey, - ) { -// httpCache(application.filesDir) { -// cacheMaxSizeBytes = (1024 * 1024 * 10) -// defaultCachePolicy = -// HttpCachePolicy.Default.CACHE_FIRST.expireAfter(20, TimeUnit.MINUTES) -// } - } - } -} - -sealed class ShopifyError : Throwable() { - object Unknown : ShopifyError() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt deleted file mode 100644 index 7565db2447..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Checkout.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.tangem.tap.common.shop.shopify.data - -import com.shopify.buy3.Storefront - -@Suppress("LongMethod", "MagicNumber") -fun Storefront.CheckoutQuery.checkoutFieldsFragment() { - ready() - webUrl() - currencyCode() - lineItemsSubtotalPrice { it.amount() } - totalPriceV2 { - it.currencyCode() - it.amount() - } - lineItems({ arg -> arg.first(250) }) { - it.edges { - it.node { -// it.id() - it.title() - it.quantity() - it.variant { - it.priceV2 { it.amount() } - } - } - } - } - shippingLine { - it.handle() - it.title() - it.priceV2 { it.amount() } - } - availableShippingRates { - it.ready() - it.shippingRates { - it.handle() - it.title() - it.priceV2 { it.amount() } - } - } - shippingAddress { - it.address1() - it.address2() - it.city() -// .company() - it.country() -// .countryCodeV2() - it.firstName() -// .formatted() -// .formattedArea() -// .id() - it.lastName() -// .latitude() -// .longitude() -// .name() - it.phone() - it.province() -// .provinceCode() - it.zip() - } - discountApplications({ arg -> arg.first(250) }) { - it.edges { - it.node { - it.onDiscountCodeApplication { - it.code() -// .applicable() -// .allocationMethod() -// .targetSelection() -// .targetType() - it.value { - it.onMoneyV2 { - it.amount() - } - it.onPricingPercentageValue { - it.percentage() - } - } - } - } - } - } - order { - it.cancelReason() - it.canceledAt() - it.currencyCode() -// .currentSubtotalPrice() -// .currentTotalDuties() -// .currentTotalPrice() -// .currentTotalTax() - it.customerLocale() - it.customerUrl() -// .discountApplications() - it.edited() - it.email() - it.financialStatus() - it.fulfillmentStatus() -// .id() -// .lineItems() -// .metafield() -// .metafields() - it.name() - it.orderNumber() -// .originalTotalDuties() -// .originalTotalPrice() - it.phone() - it.processedAt() - it.shippingAddress { - it.address1() - it.address2() - it.city() - it.company() - it.country() - it.countryCodeV2() - it.firstName() - it.formatted() - it.formattedArea() -// .id() - it.lastName() - it.latitude() - it.longitude() - it.name() - it.phone() - it.province() - it.provinceCode() - it.zip() - } -// .shippingDiscountAllocations() - it.statusUrl() -// .subtotalPriceV2() -// .successfulFulfillments() -// .totalPriceV2() -// .totalRefundedV2() -// .totalShippingPriceV2() -// .totalTaxV2() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/CheckoutItem.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/CheckoutItem.kt deleted file mode 100644 index 6d16e66908..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/CheckoutItem.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.tap.common.shop.shopify.data - -import com.shopify.graphql.support.ID - -data class CheckoutItem( - val id: ID, - val quantity: Int, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt b/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt deleted file mode 100644 index 1527fc9558..0000000000 --- a/app/src/main/java/com/tangem/tap/common/shop/shopify/data/Collection.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.tangem.tap.common.shop.shopify.data - -import com.shopify.buy3.Storefront - -@Suppress("MagicNumber") -fun Storefront.CollectionConnectionQuery.collectionFieldsFragment() { - edges { collectionEdgeQuery -> - collectionEdgeQuery - .node { collectionQuery -> - collectionQuery - .title() - .products( - { arg -> arg.first(250) }, - ) { productConnectionQuery -> - productConnectionQuery - .edges { productEdgeQuery -> - productEdgeQuery - .node { productQuery -> - productQuery.title() - .productType() - .description() - .variants({ arg -> arg.first(10) }) { variantConnectionQuery -> - variantConnectionQuery.edges { variantQuery -> - variantQuery.node { - it.title() - it.sku() - it.currentlyNotInStock() - it.priceV2 { - it.amount() - it.currencyCode() - } - it.compareAtPriceV2 { - it.amount() - it.currencyCode() - } - it.compareAtPriceV2 { - it.amount() - } - it.product { - it.title() - .productType() - .description() - } - } - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt b/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt deleted file mode 100644 index 1abbd3d17c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.tangem.tap.features.shop.data - -import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.api.tangemTech.models.ShopResponse -import com.tangem.domain.common.extensions.withIOContext -import com.tangem.tap.features.shop.domain.ShopRepository -import com.tangem.tap.features.shop.domain.models.SalesProduct -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.runCatching -import timber.log.Timber -import java.util.Locale - -/** - * Default implementation of shop feature repository - * - * @property tangemTechApi TangemTech API - * @property dispatchers coroutine dispatchers provider - * -[REDACTED_AUTHOR] - */ -internal class DefaultShopRepository( - private val tangemTechApi: TangemTechApi, - private val dispatchers: CoroutineDispatcherProvider, -) : ShopRepository { - - private val salesProductConverter = SalesProductConverter() - - override suspend fun isShopifyOrderingAvailable(): Boolean { - return runCatching(dispatchers.io) { tangemTechApi.getShopInfo(name = SHOPIFY_NAME) } - .fold( - onSuccess = ShopResponse::isOrderingAvailable, - onFailure = { - Timber.e("Server error. isShopifyOrderingAvailable returns default value (true)") - true - }, - ) - } - - override suspend fun getSalesProductInfo(): List { - return withIOContext { - val salesInfo = tangemTechApi.getSalesInfo(locale = getLocaleName(), shops = SHOPIFY_NAME) - salesProductConverter.convert(salesInfo) - } - } - - private fun getLocaleName(): String { - return if (Locale.getDefault().language == "ru") { - RU_LOCALE - } else { - EN_LOCALE - } - } - - private companion object { - private const val SHOPIFY_NAME = "shopify" - private const val RU_LOCALE = "ru" - private const val EN_LOCALE = "en" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/data/SalesProductConverter.kt b/app/src/main/java/com/tangem/tap/features/shop/data/SalesProductConverter.kt deleted file mode 100644 index 9d8f440da6..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/data/SalesProductConverter.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.tap.features.shop.data - -import com.tangem.datasource.api.tangemTech.models.SalesResponse -import com.tangem.tap.features.shop.domain.models.Notification -import com.tangem.tap.features.shop.domain.models.ProductState -import com.tangem.tap.features.shop.domain.models.ProductType -import com.tangem.tap.features.shop.domain.models.SalesProduct -import com.tangem.utils.converter.Converter - -internal class SalesProductConverter : Converter> { - - override fun convert(value: SalesResponse): List { - return value.sales.map { sales -> - val productState = when (sales.state) { - "order" -> ProductState.ORDER - "pre-order" -> ProductState.PRE_ORDER - "sold-out" -> ProductState.SOLD_OUT - else -> ProductState.SOLD_OUT - } - val productType = when (sales.product.code) { - "pack2" -> ProductType.WALLET_2_CARDS - "pack3" -> ProductType.WALLET_3_CARDS - else -> ProductType.WALLET_3_CARDS - } - SalesProduct( - id = sales.id, - productType = productType, - state = productState, - name = sales.product.name, - notification = sales.notification?.let { notification -> - Notification( - type = notification.type, - title = notification.title, - description = notification.description, - ) - }, - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt b/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt deleted file mode 100644 index a6f22bec79..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.tangem.tap.features.shop.di - -import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.tap.features.shop.data.DefaultShopRepository -import com.tangem.tap.features.shop.domain.DefaultShopifyOrderingAvailabilityUseCase -import com.tangem.tap.features.shop.domain.GetShopifySalesProductsUseCase -import com.tangem.tap.features.shop.domain.ShopRepository -import com.tangem.tap.features.shop.domain.ShopifyOrderingAvailabilityUseCase -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent -import dagger.hilt.android.scopes.ViewModelScoped - -@Module -@InstallIn(ViewModelComponent::class) -internal object ShopUseCaseModule { - - @Provides - @ViewModelScoped - fun provideShopifyOrderingAvailabilityUseCase(shopRepository: ShopRepository): ShopifyOrderingAvailabilityUseCase { - return DefaultShopifyOrderingAvailabilityUseCase( - shopRepository = shopRepository, - ) - } - - @Provides - @ViewModelScoped - fun provideGetShopifySalesProductsUseCase(shopRepository: ShopRepository): GetShopifySalesProductsUseCase { - return GetShopifySalesProductsUseCase( - shopRepository = shopRepository, - ) - } - - @Provides - @ViewModelScoped - fun provideDefaultShopRepository( - tangemTechApi: TangemTechApi, - dispatchers: CoroutineDispatcherProvider, - ): ShopRepository { - return DefaultShopRepository(tangemTechApi = tangemTechApi, dispatchers = dispatchers) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/di/ShopifyTogglesModule.kt b/app/src/main/java/com/tangem/tap/features/shop/di/ShopifyTogglesModule.kt deleted file mode 100644 index 97b5839f47..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/di/ShopifyTogglesModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.tap.features.shop.di - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.tap.features.shop.toggles.DefaultShopifyFeatureToggleManager -import com.tangem.tap.features.shop.toggles.ShopifyFeatureToggleManager -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object ShopifyTogglesModule { - - @Provides - @Singleton - fun provideDefaultShopifyFeatureToggleManager( - featureToggleManager: FeatureTogglesManager, - ): ShopifyFeatureToggleManager { - return DefaultShopifyFeatureToggleManager(featureToggleManager) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt deleted file mode 100644 index 41bbe3d449..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.tap.features.shop.domain - -/** - * Default implementation of use case to define shopify ordering availability - * - * @property shopRepository shop feature repository - * -[REDACTED_AUTHOR] - */ -internal class DefaultShopifyOrderingAvailabilityUseCase( - private val shopRepository: ShopRepository, -) : ShopifyOrderingAvailabilityUseCase { - - override suspend fun invoke() = shopRepository.isShopifyOrderingAvailable() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/GetShopifySalesProductsUseCase.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/GetShopifySalesProductsUseCase.kt deleted file mode 100644 index 7881b82dea..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/domain/GetShopifySalesProductsUseCase.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.tap.features.shop.domain - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.tap.features.shop.domain.models.SalesError -import com.tangem.tap.features.shop.domain.models.SalesProduct - -/** - * Use case to get shopify available products - * - * @property shopRepository shop feature repository - */ -class GetShopifySalesProductsUseCase( - private val shopRepository: ShopRepository, -) { - - suspend operator fun invoke(): Either> { - return try { - shopRepository.getSalesProductInfo().right() - } catch (e: Exception) { - SalesError.DataError(e).left() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt deleted file mode 100644 index 67803630b8..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.tap.features.shop.domain - -import com.tangem.tap.features.shop.domain.models.SalesProduct - -/** - * Shop feature repository - * -[REDACTED_AUTHOR] - */ -interface ShopRepository { - - /** Get shopify ordering availability */ - suspend fun isShopifyOrderingAvailable(): Boolean - - /** Get actual sales product info */ - suspend fun getSalesProductInfo(): List -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt deleted file mode 100644 index 5f6c351ce2..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.tap.features.shop.domain - -/** - * Use case to define shopify ordering availability - * -[REDACTED_AUTHOR] - */ -internal interface ShopifyOrderingAvailabilityUseCase { - - /** Get availability */ - suspend operator fun invoke(): Boolean -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/models/ProductType.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/models/ProductType.kt deleted file mode 100644 index 0987fdd8bd..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/domain/models/ProductType.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.tap.features.shop.domain.models - -private const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S" -private const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S" - -enum class ProductType(val sku: String) { - - WALLET_2_CARDS(TANGEM_WALLET_2_CARDS_SKU), - WALLET_3_CARDS(TANGEM_WALLET_3_CARDS_SKU), - ; - - companion object { - val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU) - fun fromSku(sku: String): ProductType? { - return when (sku) { - WALLET_2_CARDS.sku -> WALLET_2_CARDS - WALLET_3_CARDS.sku -> WALLET_3_CARDS - else -> null - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/models/SalesError.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/models/SalesError.kt deleted file mode 100644 index 373b8d14c8..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/domain/models/SalesError.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.tangem.tap.features.shop.domain.models - -sealed class SalesError { - data class DataError(val cause: Throwable) : SalesError() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/models/SalesProduct.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/models/SalesProduct.kt deleted file mode 100644 index b3b5a083d3..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/domain/models/SalesProduct.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.tangem.tap.features.shop.domain.models - -/** - * Sales product - * - * @property id product id - * @property productType shows TW2 cards or 3 cards - * @property state state as order available etc - * @property name product name - * @property notification optional notification - */ -data class SalesProduct( - val id: String, - val productType: ProductType, - val state: ProductState, - val name: String, - val notification: Notification?, -) - -data class Notification( - val type: String, - val title: String, - val description: String, -) - -enum class ProductState { - ORDER, - SOLD_OUT, - PRE_ORDER, -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt b/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt deleted file mode 100644 index 2e39e2bd4f..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.tap.features.shop.presentation - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.tangem.tap.features.shop.domain.GetShopifySalesProductsUseCase -import com.tangem.tap.features.shop.domain.ShopifyOrderingAvailabilityUseCase -import com.tangem.tap.features.shop.redux.ShopAction -import com.tangem.tap.proxy.AppStateHolder -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.runCatching -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.launch -import javax.inject.Inject - -/** - * Shop screen view model - * - * @property shopifyOrderingAvailabilityUseCase use case to define shopify ordering availability - * @property getShopifySalesProductsUseCase use case to get actual sales info - * @property dispatchers coroutine dispatchers provider - * @property appStateHolder redux state holder - * -[REDACTED_AUTHOR] - */ -@HiltViewModel -internal class ShopViewModel @Inject constructor( - private val shopifyOrderingAvailabilityUseCase: ShopifyOrderingAvailabilityUseCase, - private val getShopifySalesProductsUseCase: GetShopifySalesProductsUseCase, - private val dispatchers: CoroutineDispatcherProvider, - private val appStateHolder: AppStateHolder, -) : ViewModel() { - - /** Check ordering delay block visibility */ - fun checkOrderingDelayBlockVisibility() { - viewModelScope.launch(dispatchers.main) { - val visibility = runCatching(dispatchers.io) { shopifyOrderingAvailabilityUseCase() } - .fold(onSuccess = { !it }, onFailure = { false }) - - appStateHolder.mainStore?.dispatch(action = ShopAction.SetOrderingDelayBlockVisibility(visibility)) - } - } - - /** - * Get actual sales products info - * to configure view dynamically - */ - fun getActualSalesInfo() { - viewModelScope.launch(dispatchers.main) { - val action = getShopifySalesProductsUseCase().fold( - ifLeft = { - ShopAction.SalesProductsError - }, - ifRight = { - ShopAction.SalesProductsLoaded(it) - }, - ) - appStateHolder.mainStore?.dispatch(action) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt deleted file mode 100644 index a72324e845..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.tangem.tap.features.shop.redux - -import android.content.Intent -import com.tangem.tap.common.redux.NotificationAction -import com.tangem.tap.features.shop.domain.models.ProductType -import com.tangem.tap.common.shop.data.TangemProduct -import com.tangem.tap.common.shop.googlepay.GooglePayService -import com.tangem.tap.features.shop.domain.models.SalesProduct -import com.tangem.wallet.R -import org.rekotlin.Action - -sealed interface ShopAction : Action { - - object LoadProducts : ShopAction { - data class Success(val products: List) : ShopAction - object Failure : ShopAction, NotificationAction { - override val messageResource = R.string.common_server_unavailable - } - } - - data class ApplyPromoCode(val promoCode: String) : ShopAction { - data class Success(val promoCode: String?, val products: List) : ShopAction - object InvalidPromoCode : ShopAction - } - - object BuyWithGooglePay : ShopAction { - object UserCancelled : ShopAction - data class HandleGooglePayResponse(val resultCode: Int, val data: Intent?) : ShopAction - - data class Failure(val exception: Throwable) : ShopAction - object Success : ShopAction - } - - object StartWebCheckout : ShopAction - - data class CheckIfGooglePayAvailable(val googlePayService: GooglePayService) : ShopAction { - object Success : ShopAction - object Failure : ShopAction - } - - data class SelectProduct(val productType: ProductType) : ShopAction - - object FinishSuccessfulOrder : ShopAction - - object ResetState : ShopAction - - data class SetOrderingDelayBlockVisibility(val visibility: Boolean) : ShopAction - - data class SalesProductsLoaded(val salesProducts: List) : ShopAction - - object SalesProductsError : ShopAction -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt deleted file mode 100644 index f049ef01b6..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopMiddleware.kt +++ /dev/null @@ -1,128 +0,0 @@ -package com.tangem.tap.features.shop.redux - -import com.tangem.core.analytics.Analytics -import com.tangem.core.navigation.AppScreen -import com.tangem.core.navigation.NavigationAction -import com.tangem.tap.common.analytics.events.Shop -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.dispatchOpenUrl -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.scope -import com.tangem.tap.shopService -import com.tangem.tap.store -import kotlinx.coroutines.launch -import org.rekotlin.Action -import org.rekotlin.Middleware -import timber.log.Timber - -class ShopMiddleware { - - val shopMiddleware: Middleware = { dispatch, state -> - { next -> - { action -> - handle(action) - next(action) - } - } - } -} - -@Suppress("LongMethod", "ComplexMethod") -private fun handle(action: Action) { - val shopState = store.state.shopState - - if (action is NavigationAction.NavigateTo && action.screen == AppScreen.Shop) { - store.dispatch(ShopAction.LoadProducts) - } - - if (action !is ShopAction) return - - when (action) { - is ShopAction.ApplyPromoCode -> { - scope.launch { - if (action.promoCode.isBlank() && shopState.promoCode == null) { - store.dispatchOnMain(ShopAction.ApplyPromoCode.InvalidPromoCode) - return@launch - } - - val result = shopService.applyPromoCode(action.promoCode) - - result.onSuccess { products -> - store.dispatchOnMain( - ShopAction.ApplyPromoCode.Success( - promoCode = products.first { it.type == shopState.selectedProduct }.appliedDiscount, - products = products, - ), - ) - } - result.onFailure { store.dispatchOnMain(ShopAction.ApplyPromoCode.InvalidPromoCode) } - } - } - ShopAction.BuyWithGooglePay -> { - shopService.buyWithGooglePay(shopState.selectedProduct) -// shopService.subscribeToGooglePayResult(productType = shopState.selectedProduct) { result -> -// result.onSuccess { -// store.dispatch(ShopAction.BuyWithGooglePay.Success) -// } -// result.onFailure { error -> -// if (error is TangemSdkError.UserCancelled) { -// store.dispatch(ShopAction.BuyWithGooglePay.UserCancelled) -// } else { -// store.dispatch(ShopAction.BuyWithGooglePay.Failure(error)) -// } -// } -// } - } - is ShopAction.BuyWithGooglePay.HandleGooglePayResponse -> { - scope.launch { - val result = shopService.handleGooglePayResult( - action.resultCode, - action.data, - shopState.selectedProduct, - ) - result.onSuccess { - store.dispatchOnMain(ShopAction.BuyWithGooglePay.Success) - } - result.onFailure { - store.dispatchOnMain(ShopAction.BuyWithGooglePay.Failure(it)) - } - } - } - ShopAction.LoadProducts -> { - scope.launch { - shopService.getProducts().fold( - onSuccess = { store.dispatchOnMain(ShopAction.LoadProducts.Success(it)) }, - onFailure = { - Timber.e(it) - store.dispatchOnMain(ShopAction.LoadProducts.Failure) - }, - ) - } - } - is ShopAction.CheckIfGooglePayAvailable -> { - scope.launch { - val isAvailable = - shopService.checkIfGooglePayAvailable(action.googlePayService).getOrNull() - ?: false - val newAction = if (isAvailable) { - ShopAction.CheckIfGooglePayAvailable.Success - } else { - ShopAction.CheckIfGooglePayAvailable.Failure - } - store.dispatchOnMain(newAction) - } - } - ShopAction.StartWebCheckout -> { - Analytics.send(Shop.Redirected(null)) - store.dispatchOpenUrl(shopService.getCheckoutUrl(shopState.selectedProduct)) - store.dispatch(ShopAction.FinishSuccessfulOrder) - } - - is ShopAction.FinishSuccessfulOrder -> { - scope.launch { - shopService.waitForCheckout(shopState.selectedProduct) - } - } - else -> {} - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt deleted file mode 100644 index 11061c7abf..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.tangem.tap.features.shop.redux - -import org.rekotlin.Action - -object ShopReducer { - fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state) -} - -private fun internalReduce(action: Action, state: ShopState): ShopState { - if (action !is ShopAction) return state - - return when (action) { - is ShopAction.ApplyPromoCode -> state.copy(promoCode = action.promoCode, promoCodeLoading = true) - is ShopAction.LoadProducts.Success -> state.copy(availableProducts = action.products) - is ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy(promoCode = null, promoCodeLoading = false) - is ShopAction.ApplyPromoCode.Success -> { - state.copy( - promoCode = action.promoCode, - availableProducts = action.products, - promoCodeLoading = false, - ) - } - is ShopAction.SelectProduct -> state.copy(selectedProduct = action.productType) - - // TODO: change when we add support for GPay - is ShopAction.CheckIfGooglePayAvailable.Failure -> state.copy(isGooglePayAvailable = false) - is ShopAction.CheckIfGooglePayAvailable.Success -> state.copy(isGooglePayAvailable = false) - - is ShopAction.ResetState -> ShopState() - is ShopAction.SetOrderingDelayBlockVisibility -> state.copy(isOrderingDelayBlockVisible = action.visibility) - is ShopAction.BuyWithGooglePay, - is ShopAction.LoadProducts, - is ShopAction.StartWebCheckout, - is ShopAction.CheckIfGooglePayAvailable, - is ShopAction.BuyWithGooglePay.Failure, - is ShopAction.BuyWithGooglePay.HandleGooglePayResponse, - is ShopAction.BuyWithGooglePay.Success, - is ShopAction.BuyWithGooglePay.UserCancelled, - is ShopAction.FinishSuccessfulOrder, - is ShopAction.LoadProducts.Failure, - -> state - is ShopAction.SalesProductsLoaded -> state.copy( - salesProducts = action.salesProducts, - ) - is ShopAction.SalesProductsError -> state.copy( - salesProducts = emptyList(), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt deleted file mode 100644 index 08bac48f4d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.tap.features.shop.redux - -import com.tangem.tap.features.shop.domain.models.ProductType -import com.tangem.tap.common.shop.data.TangemProduct -import com.tangem.tap.features.shop.domain.models.SalesProduct -import org.rekotlin.StateType - -data class ShopState( - val availableProducts: List = emptyList(), - val selectedProduct: ProductType = ProductType.WALLET_3_CARDS, - val salesProducts: List = emptyList(), - val promoCode: String? = null, - val promoCodeLoading: Boolean = false, - val isGooglePayAvailable: Boolean = false, // TODO: change when we add support for GPay - val isOrderingDelayBlockVisible: Boolean = false, -) : StateType { - val total: String? - get() = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum?.finalValue - - val priceBeforeDiscount: String? - get() { - val totalSum = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum - if (totalSum?.finalValue != totalSum?.beforeDiscount) { - return totalSum?.beforeDiscount - } - return null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/toggles/DefaultShopifyFeatureToggleManager.kt b/app/src/main/java/com/tangem/tap/features/shop/toggles/DefaultShopifyFeatureToggleManager.kt deleted file mode 100644 index eda902f707..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/toggles/DefaultShopifyFeatureToggleManager.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.tap.features.shop.toggles - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager - -internal class DefaultShopifyFeatureToggleManager( - private val featureTogglesManager: FeatureTogglesManager, -) : ShopifyFeatureToggleManager { - - override val isDynamicSalesProductsEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled("SHOPIFY_DYNAMIC_ENABLED") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/toggles/ShopifyFeatureToggleManager.kt b/app/src/main/java/com/tangem/tap/features/shop/toggles/ShopifyFeatureToggleManager.kt deleted file mode 100644 index ab3b8fc968..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/toggles/ShopifyFeatureToggleManager.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.tap.features.shop.toggles - -/** - * Shopify feature toggle manager that provides info about shopify toggle availability - * - */ -interface ShopifyFeatureToggleManager { - - val isDynamicSalesProductsEnabled: Boolean -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt deleted file mode 100644 index bfb86a0f8a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt +++ /dev/null @@ -1,251 +0,0 @@ -package com.tangem.tap.features.shop.ui - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.content.Context -import android.os.Bundle -import android.view.View -import android.view.View.OnFocusChangeListener -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.viewModels -import by.kirich1409.viewbindingdelegate.viewBinding -import com.tangem.core.navigation.NavigationAction -import com.tangem.tap.common.GlobalLayoutStateHandler -import com.tangem.tap.common.KeyboardObserver -import com.tangem.tap.common.extensions.getQuantityString -import com.tangem.tap.common.extensions.show -import com.tangem.tap.features.BaseStoreFragment -import com.tangem.tap.features.shop.domain.models.ProductState -import com.tangem.tap.features.shop.domain.models.ProductType -import com.tangem.tap.features.shop.domain.models.SalesProduct -import com.tangem.tap.features.shop.presentation.ShopViewModel -import com.tangem.tap.features.shop.redux.ShopAction -import com.tangem.tap.features.shop.redux.ShopState -import com.tangem.tap.features.shop.toggles.ShopifyFeatureToggleManager -import com.tangem.tap.store -import com.tangem.wallet.R -import com.tangem.wallet.databinding.FragmentShopBinding -import dagger.hilt.android.AndroidEntryPoint -import org.rekotlin.StoreSubscriber -import javax.inject.Inject - -@AndroidEntryPoint -internal class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber { - - @Inject - lateinit var shopifyFeatureToggleManager: ShopifyFeatureToggleManager - - private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind) - private var cardTranslationY = 70f - - private lateinit var keyboardObserver: KeyboardObserver - - private val viewModel by viewModels() - - override fun subscribeToStore() { - store.subscribe(this) { state -> - state.skipRepeats { oldState, newState -> - oldState.shopState == newState.shopState - }.select { it.shopState } - } - storeSubscribersList.add(this) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - if (shopifyFeatureToggleManager.isDynamicSalesProductsEnabled) { - viewModel.getActualSalesInfo() - } else { - viewModel.checkOrderingDelayBlockVisibility() - } - - activity?.onBackPressedDispatcher?.addCallback( - this, - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(ShopAction.ResetState) - } - }, - ) - } - - override fun onDestroyView() { - super.onDestroyView() - keyboardObserver.unregisterListener() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - setupCardsImages() - setupProductSelection() - setupPromoCodeEditText() - - binding.toolbar.setNavigationOnClickListener { - requireActivity().onBackPressed() - } - - keyboardObserver = KeyboardObserver(requireActivity()).apply { - registerListener { isVisible -> - binding.flCards.show(!isVisible) - } - } - } - - @Suppress("MagicNumber") - private fun setupCardsImages() { - GlobalLayoutStateHandler(binding.imvSecond).apply { - onStateChanged = { - cardTranslationY = it.height * 0.15f - binding.imvSecond.animate() - .translationY(cardTranslationY) - .scaleX(0.9f) - .scaleY(0.9f) - .start() - binding.imvThird.animate() - .translationY(cardTranslationY * 2) - .scaleX(0.8f) - .scaleY(0.8f) - .start() - detach() - } - } - } - - private fun setupProductSelection() = with(binding) { - chipProduct1.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_3_CARDS)) - } - chipProduct2.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_2_CARDS)) - } - chipProduct1.text = chipProduct1.getQuantityString(R.plurals.card_label_card_count, quantity = 3) - chipProduct2.text = chipProduct2.getQuantityString(R.plurals.card_label_card_count, quantity = 2) - } - - private fun setupPromoCodeEditText() = with(binding) { - etPromoCode.setOnEditorActionListener { view, actionId, event -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - val imm: InputMethodManager = - requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(view.windowToken, 0) - view.clearFocus() - return@setOnEditorActionListener true - } - return@setOnEditorActionListener false - } - - etPromoCode.onFocusChangeListener = OnFocusChangeListener { _, hasFocus -> - if (!hasFocus) { - store.dispatch(ShopAction.ApplyPromoCode(etPromoCode.text.toString())) - } - } - } - - override fun newState(state: ShopState) { - if (activity == null || view == null) return - - animateProductSelection(state.selectedProduct) - handlePriceState(state) - handlePromoCodeState(state) - // TODO: https://tangem.slack.com/archives/C01HARKDLQ0/p1691421861756069 - // if (shopifyFeatureToggleManager.isDynamicSalesProductsEnabled) { - // handleNotificationBlock(state) - // } else { - // handleOrderingDelayBlock(isVisible = state.isOrderingDelayBlockVisible) - // } - handleButtonsState(state) - } - - private fun animateProductSelection(selectedProduct: ProductType) { - val show = when (selectedProduct) { - ProductType.WALLET_2_CARDS -> false - ProductType.WALLET_3_CARDS -> true - } - showOrHideThirdCardWithAnimation(show) - } - - private fun showOrHideThirdCardWithAnimation(show: Boolean) = with(binding) { - val translationY = if (show) cardTranslationY * 2 else cardTranslationY - if (show) imvThird.show() - imvThird.animate() - .translationY(translationY) - .setListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - super.onAnimationEnd(animation) - imvThird.show(show) - } - }, - ) - } - - private fun handlePriceState(state: ShopState) = with(binding) { - tvTotal.text = state.total - tvTotalBeforeDiscount.text = state.priceBeforeDiscount - - pbPrice.show(state.total == null) - } - - private fun handlePromoCodeState(state: ShopState) = with(binding) { - if (state.promoCode == null && !etPromoCode.hasFocus()) { - etPromoCode.setText("") - } - pbPromoCode.show(state.promoCodeLoading) - } - - // private fun handleOrderingDelayBlock(isVisible: Boolean) { - // if (isVisible) binding.tvSoldOutDesc.show() else binding.tvSoldOutDesc.hide() - // } - // - // private fun handleNotificationBlock(state: ShopState) { - // if (isVisible) { - // binding.tvSoldOutDesc.show() - // getSelectedSalesProduct(state)?.notification?.let { notification -> - // binding.tvSoldOutDesc.text = notification.description - // } - // } else { - // binding.tvSoldOutDesc.hide() - // } - // } - - private fun handleButtonsState(state: ShopState) = with(binding) { - btnPayGooglePay.root.show(state.isGooglePayAvailable) - btnAlternativePayment.show(state.isGooglePayAvailable) - btnMainAction.show(!state.isGooglePayAvailable) - - if (state.total != null) { - btnAlternativePayment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) } - btnPayGooglePay.root.setOnClickListener { store.dispatch(ShopAction.BuyWithGooglePay) } - btnMainAction.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) } - if (state.salesProducts.isNotEmpty()) { - getSelectedSalesProduct(state)?.let { selectedProduct -> - btnMainAction.text = getMainBtnTextByProductState( - productState = selectedProduct.state, - ) - } - } - } - } - - override fun handleOnBackPressed() { - store.dispatch(ShopAction.ResetState) - super.handleOnBackPressed() - } - - private fun getSelectedSalesProduct(state: ShopState): SalesProduct? { - return state.salesProducts.find { - it.productType == state.selectedProduct - } - } - - private fun getMainBtnTextByProductState(productState: ProductState): String = when (productState) { - ProductState.ORDER -> getString(R.string.shop_buy_now) - ProductState.SOLD_OUT -> "Sold out" // getString(R.string.sold_out) // todo finalize in next PR - ProductState.PRE_ORDER -> "Pre order" // getString(R.string.pre_order) // todo finalize in next PR - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_shop.xml b/app/src/main/res/layout/fragment_shop.xml deleted file mode 100644 index 35df4922f4..0000000000 --- a/app/src/main/res/layout/fragment_shop.xml +++ /dev/null @@ -1,336 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt index 580334eb2b..8ebac44c3b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt @@ -102,7 +102,6 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager { ), appsFlyerDevKey = configValues.appsFlyer.appsFlyerDevKey, amplitudeApiKey = configValues.amplitudeApiKey, - shopify = configValues.shopifyShop, sprinklr = configValues.sprinklr, walletConnectProjectId = configValues.walletConnectProjectId, tangemComAuthorization = configValues.tangemComAuthorization, diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt index 8490f44237..25e8595d33 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt @@ -14,7 +14,6 @@ data class Config( val isTopUpEnabled: Boolean = false, @Deprecated("Not relevant since version 3.23") val isCreatingTwinCardsAllowed: Boolean = false, - val shopify: ShopifyShop? = null, val sprinklr: SprinklrConfig? = null, val walletConnectProjectId: String = "", val tangemComAuthorization: String? = null, diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt index eaedb32084..0e572bd3e5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt @@ -31,7 +31,6 @@ class ConfigValueModel( val blockcypherTokens: Set?, val infuraProjectId: String?, val appsFlyer: AppsFlyer, - val shopifyShop: ShopifyShop?, val sprinklr: SprinklrConfig?, val tronGridApiKey: String, val amplitudeApiKey: String, diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/ShopifyShop.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/ShopifyShop.kt deleted file mode 100644 index c4a09adcfe..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/config/models/ShopifyShop.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.datasource.config.models - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class ShopifyShop( - @Json(name = "domain") - val domain: String, - @Json(name = "storefrontApiKeyAndroid") - val storefrontApiKey: String, - @Json(name = "merchantID") - val merchantID: String?, -) \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 1b7b643cea..ac9325b011 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -3,10 +3,6 @@ "name": "NEW_CARD_SCANNING_ENABLED", "version": "undefined" }, - { - "name": "SHOPIFY_DYNAMIC_ENABLED", - "version": "undefined" - }, { "name": "REDESIGNED_MANAGE_TOKENS_SCREEN_ENABLED", "version": "undefined" diff --git a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt index 7ca534e15c..a0a6d64f25 100644 --- a/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt +++ b/core/navigation/src/main/java/com/tangem/core/navigation/NavigationState.kt @@ -11,7 +11,6 @@ data class NavigationState( enum class AppScreen(val isDialogFragment: Boolean = false) { Home, - Shop, Disclaimer, OnboardingNote, OnboardingWallet, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 02f4d4bad6..96db197034 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -61,7 +61,6 @@ okhttp = "4.9.3" rekotlin = "1.0.4" retrofit = "2.8.1" retrofitMoshiConverter = "2.6.0" -shopifyBuySdk = "12.0.0" spongycastleCryptoCore = "1.58.0.0" timber = "4.7.1" viewBindingDelegate = "1.5.9" @@ -224,7 +223,6 @@ moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okHttp-prettyLogging = { module = "com.github.ihsanbal:LoggingInterceptor", version.ref = "okHttp-prettyLogging" } -shopify-buy = { module = "com.shopify.mobilebuysdk:buy3", version.ref = "shopifyBuySdk" } spongecastle-core = { module = "com.madgag.spongycastle:core", version.ref = "spongycastleCryptoCore" } reKotlin = { module = "org.rekotlin:rekotlin", version.ref = "rekotlin" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }