Updated on 2026-08-14

This commit is contained in:
Tangem 2022-02-18 12:42:00 +03:00
parent 304bc23a3c
commit 20421a758c
45 changed files with 1599 additions and 10 deletions

View file

@ -19,6 +19,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCar
import com.tangem.tap.features.onboarding.products.twins.ui.TwinsCardsFragment
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.shop.ui.ShopFragment
import com.tangem.tap.features.tokens.ui.AddTokensFragment
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
@ -69,6 +70,7 @@ fun FragmentActivity.addOnBackPressedDispatcher(
private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
AppScreen.Home -> HomeFragment()
AppScreen.Shop -> ShopFragment()
AppScreen.OnboardingNote -> OnboardingNoteFragment()
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
AppScreen.OnboardingTwins -> TwinsCardsFragment()

View file

@ -11,6 +11,7 @@ import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOt
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.send.redux.reducers.SendScreenReducer
import com.tangem.tap.features.shop.redux.ShopReducer
import com.tangem.tap.features.tokens.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
import org.rekotlin.Action
@ -32,7 +33,8 @@ fun appReducer(action: Action, state: AppState?): AppState {
detailsState = DetailsReducer.reduce(action, state),
disclaimerState = DisclaimerReducer.reduce(action, state),
tokensState = TokensReducer.reduce(action, state),
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState)
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
shopState = ShopReducer.reduce(action, state.shopState),
)
}

View file

@ -23,6 +23,8 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWallet
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
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.tokens.redux.TokensMiddleware
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.wallet.redux.WalletState
@ -44,6 +46,7 @@ data class AppState(
val disclaimerState: DisclaimerState = DisclaimerState(),
val tokensState: TokensState = TokensState(),
val walletConnectState: WalletConnectState = WalletConnectState(),
val shopState: ShopState = ShopState(),
) : StateType {
companion object {
@ -62,7 +65,8 @@ data class AppState(
DisclaimerMiddleware().disclaimerMiddleware,
TokensMiddleware().tokensMiddleware,
WalletConnectMiddleware().walletConnectMiddleware,
BackupMiddleware().backupMiddleware
BackupMiddleware().backupMiddleware,
ShopMiddleware().shopMiddleware,
)
}
}

View file

@ -11,6 +11,7 @@ data class NavigationState(
enum class AppScreen {
Home,
Shop,
Disclaimer,
OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther,
Wallet, WalletDetails,

View file

@ -0,0 +1,216 @@
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.tap.common.shop.data.ProductType
import com.tangem.tap.common.shop.data.TangemProduct
import com.tangem.tap.common.shop.data.TotalSum
import com.tangem.tap.common.shop.shopify.ShopifyShop
import com.tangem.tap.common.shop.shopify.data.CheckoutItem
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import java.math.BigDecimal
import java.util.*
class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
private val shopifyService = ShopifyService(application, shopifyShop)
private lateinit var product: Storefront.Product
private val checkouts = mutableMapOf<ProductType, Storefront.Checkout>()
private val variants = mutableMapOf<ProductType, Storefront.ProductVariant>()
private lateinit var googlePayService: GooglePayService
suspend fun getProducts(): Result<List<TangemProduct>> {
val result = shopifyService.getProducts()
result.onSuccess {
product = it.first {
val variantsSku = it.variants.edges.map { it.node.sku }
variantsSku.contains(ProductType.WALLET_2_CARDS.sku) && variantsSku.contains(
ProductType.WALLET_3_CARDS.sku
)
}
product.variants.edges.map { it.node }
.forEach { variant ->
if (variant.sku == ProductType.WALLET_2_CARDS.sku) {
variants[ProductType.WALLET_2_CARDS] = variant
} else if (variant.sku == ProductType.WALLET_3_CARDS.sku) {
variants[ProductType.WALLET_3_CARDS] = variant
}
}
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))
}
return Result.failure(result.exceptionOrNull()!!)
}
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<Boolean> {
this.googlePayService = googlePayService
return googlePayService.checkIfGooglePayAvailable()
}
fun buyWithGooglePay(productType: ProductType) {
val totalPrice = checkouts[productType]!!.totalPriceV2.amount
googlePayService.payWithGooglePay(
totalPriceCents = totalPrice, currencyCode = checkouts[productType]!!.currencyCode.name,
merchantID = shopifyService.shop.merchantID
)
}
// fun subscribeToGooglePayResult(
// productType: ProductType,
// resultCallback: (Result<PaymentData>) -> Unit
// ) {
// googlePayService.responseCallback = { result ->
// result.onFailure { }
// result.onSuccess {
// completeTokenizedPayment(it, productType)
// }
// }
// }
suspend fun handleGooglePayResult(
resultCode: Int,
data: Intent?,
productType: ProductType
): Result<Unit> {
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<Storefront.Checkout> {
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<List<TangemProduct>> {
val products = variants.keys
.map { coroutineScope { async { applyPromoCode(promoCode, it) } } }
.awaitAll()
.map { result -> result.getOrElse { return Result.failure(it) } }
return Result.success(products)
}
suspend fun applyPromoCode(promoCode: String, productType: ProductType): Result<TangemProduct> {
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
}
companion object {
const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2"
const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3"
}
}
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
}

View file

@ -2,6 +2,7 @@ package com.tangem.tap.domain.configurable.config
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.common.extensions.VoidCallback
import com.tangem.tap.common.shop.shopify.ShopifyShop
import com.tangem.tap.domain.configurable.Loader
/**
@ -16,7 +17,8 @@ data class Config(
val isSendingToPayIdEnabled: Boolean = true,
val isTopUpEnabled: Boolean = false,
@Deprecated("Not relevant since version 3.23")
val isCreatingTwinCardsAllowed: Boolean = false
val isCreatingTwinCardsAllowed: Boolean = false,
val shopify: ShopifyShop? = null
)
class ConfigManager(
@ -52,10 +54,10 @@ class ConfigManager(
fun resetToDefault(name: String) {
when (name) {
isSendingToPayIdEnabled -> config =
config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
isTopUpEnabled -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
isCreatingTwinCardsAllowed -> config =
config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
}
}
@ -87,6 +89,7 @@ class ConfigManager(
infuraProjectId = values.infuraProjectId
),
appsFlyerDevKey = values.appsFlyerDevKey,
shopify = values.shopify,
)
defaultConfig = defaultConfig.copy(
coinMarketCapKey = values.coinMarketCapKey,
@ -99,6 +102,7 @@ class ConfigManager(
infuraProjectId = values.infuraProjectId
),
appsFlyerDevKey = values.appsFlyerDevKey,
shopify = values.shopify,
)
}

View file

@ -1,5 +1,7 @@
package com.tangem.tap.domain.configurable.config
import com.tangem.tap.common.shop.shopify.ShopifyShop
/**
[REDACTED_AUTHOR]
*/
@ -20,6 +22,7 @@ class ConfigValueModel(
val blockcypherTokens: Set<String>?,
val infuraProjectId: String?,
val appsFlyerDevKey: String,
val shopify: ShopifyShop?
)
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {

View file

@ -4,7 +4,6 @@ import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.common.analytics.GetCardSourceParams
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.extensions.dispatchOpenUrl
import com.tangem.tap.common.extensions.onCardScanned
import com.tangem.tap.common.extensions.withMainContext
import com.tangem.tap.common.post
@ -50,7 +49,7 @@ private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
}
is HomeAction.ReadCard -> handleReadCard()
is HomeAction.GoToShop -> {
store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop))
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.GET_CARD,
params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param)

View file

@ -32,7 +32,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
StoreSubscriber<OnboardingWalletState>, FragmentOnBackPressedHandler {
private var accessCodeDialog: AccessCodeDialog? = null
private lateinit var cardsWidget: BackupCardsWidget
private lateinit var cardsWidget: WalletCardsWidget
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -48,7 +48,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet),
super.onViewCreated(view, savedInstanceState)
val leapfrog = LeapfrogWidget(fl_cards_container)
cardsWidget = BackupCardsWidget(leapfrog) { 200f }
cardsWidget = WalletCardsWidget(leapfrog) { 200f }
startPostponedEnterTransition()
view_pager_backup_info.adapter = BackupInfoAdapter()

View file

@ -10,7 +10,7 @@ import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState
import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget
class BackupCardsWidget(
class WalletCardsWidget(
val leapfrogWidget: LeapfrogWidget,
val getTopOfAnchorViewForActivateState: () -> Float,
) {

View file

@ -0,0 +1,38 @@
package com.tangem.tap.features.shop.redux
import android.content.Intent
import com.tangem.tap.common.shop.GooglePayService
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.common.shop.data.TangemProduct
import org.rekotlin.Action
sealed class ShopAction : Action {
object LoadProducts : ShopAction() {
data class Success(val products: List<TangemProduct>) : ShopAction()
}
data class ApplyPromoCode(val promoCode: String) : ShopAction() {
data class Success(val promoCode: String?, val products: List<TangemProduct>) : 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 ResetState : ShopAction()
}

View file

@ -0,0 +1,116 @@
package com.tangem.tap.features.shop.redux
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.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
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
class ShopMiddleware {
val shopMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
handle(action)
next(action)
}
}
}
}
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 {
val result = shopService.getProducts()
result.onSuccess {
store.dispatchOnMain(ShopAction.LoadProducts.Success(it))
}
}
}
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 -> {
store.dispatchOpenUrl(shopService.getCheckoutUrl(shopState.selectedProduct))
}
}
}

View file

@ -0,0 +1,68 @@
package com.tangem.tap.features.shop.redux
import org.rekotlin.Action
class ShopReducer {
companion object {
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
)
ShopAction.BuyWithGooglePay -> state
ShopAction.LoadProducts -> state
is ShopAction.LoadProducts.Success -> {
state.copy(
availableProducts = action.products,
)
}
ShopAction.StartWebCheckout -> state
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,
)
}
is ShopAction.CheckIfGooglePayAvailable -> {
state
}
ShopAction.CheckIfGooglePayAvailable.Failure -> {
state.copy(isGooglePayAvailable = false)
}
ShopAction.CheckIfGooglePayAvailable.Success -> {
state.copy(isGooglePayAvailable = false) // TODO: change when we add support for GPay
}
is ShopAction.BuyWithGooglePay.Failure -> {
state
}
is ShopAction.BuyWithGooglePay.HandleGooglePayResponse -> {
state
}
ShopAction.BuyWithGooglePay.Success -> {
state
}
ShopAction.BuyWithGooglePay.UserCancelled -> {
state
}
ShopAction.ResetState -> ShopState()
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.tap.features.shop.redux
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.common.shop.data.TangemProduct
import org.rekotlin.StateType
data class ShopState(
val availableProducts: List<TangemProduct> = emptyList(),
val selectedProduct: ProductType = ProductType.WALLET_3_CARDS,
val promoCode: String? = null,
val promoCodeLoading: Boolean = false,
val isGooglePayAvailable: Boolean = false, // TODO: change when we add support for GPay
) : 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
}
}

View file

@ -0,0 +1,167 @@
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 com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.shop.redux.ShopAction
import com.tangem.tap.features.shop.redux.ShopState
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_shop.*
import org.rekotlin.StoreSubscriber
class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<ShopState> {
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)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(ShopAction.ResetState)
}
})
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupCardsImages()
setupProductSelection()
setupPromoCodeEditText()
toolbar.setNavigationOnClickListener {
requireActivity().onBackPressed()
}
val keyboardObserver = KeyboardObserver(requireActivity())
keyboardObserver.registerListener { isVisible ->
fl_cards.show(!isVisible)
}
}
private fun setupCardsImages() {
imv_second.animate()
.translationY(70f)
.scaleX(0.9f)
.scaleY(0.9f)
.start()
imv_third.animate()
.translationY(140f)
.scaleX(0.8f)
.scaleY(0.8f)
.start()
}
private fun setupProductSelection() {
chip_product_1.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_3_CARDS))
}
chip_product_2.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) store.dispatch(ShopAction.SelectProduct(ProductType.WALLET_2_CARDS))
}
}
private fun setupPromoCodeEditText() {
et_promo_code.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
}
et_promo_code.onFocusChangeListener = OnFocusChangeListener { _, hasFocus ->
if (!hasFocus) {
store.dispatch(ShopAction.ApplyPromoCode(et_promo_code.text.toString()))
}
}
}
override fun newState(state: ShopState) {
if (activity == null) return
animateProductSelection(state.selectedProduct)
handlePriceState(state)
handlePromoCodeState(state)
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) {
val translationY = if (show) 140f else 0f
if (show) imv_third.show()
imv_third.animate()
.translationY(translationY)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
imv_third?.show(show)
}
})
}
private fun handlePriceState(state: ShopState) {
tv_total.text = state.total
tv_total_before_discount.text = state.priceBeforeDiscount
pb_price.show(state.total == null)
}
private fun handlePromoCodeState(state: ShopState) {
if (state.promoCode == null && !et_promo_code.hasFocus()) {
et_promo_code.setText("")
}
pb_promo_code.show(state.promoCodeLoading)
}
private fun handleButtonsState(state: ShopState) {
btn_pay_google_pay.show(state.isGooglePayAvailable)
btn_alternative_payment.show(state.isGooglePayAvailable)
btn_main_action.show(!state.isGooglePayAvailable)
if (state.total != null) {
btn_alternative_payment.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
btn_main_action.setOnClickListener { store.dispatch(ShopAction.StartWebCheckout) }
btn_pay_google_pay.setOnClickListener { store.dispatch(ShopAction.BuyWithGooglePay) }
}
}
override fun handleOnBackPressed() {
store.dispatch(ShopAction.ResetState)
super.handleOnBackPressed()
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 24% opacity -->
<item android:color="@color/chipBlack" android:state_enabled="true" android:state_selected="true" />
<item android:color="@color/chipBlack" android:state_checked="true" android:state_enabled="true" />
<item android:color="@color/backgroundGray" android:state_enabled="true" />
<item android:color="@color/backgroundGray" />
</selector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 24% opacity -->
<item android:color="@color/backgroundGray" android:state_enabled="true" android:state_selected="true" />
<item android:color="@color/backgroundGray" android:state_checked="true" android:state_enabled="true" />
<item android:color="@color/chipBlack" android:state_enabled="true" />
<item android:color="@color/chipBlack" />
</selector>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

View file

@ -0,0 +1,54 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="103dp"
android:height="17dp"
android:viewportWidth="103.0"
android:viewportHeight="17.0">
<path
android:pathData="M0.148,2.976L3.914,2.976C4.446,2.976 4.938,3.093 5.391,3.326C5.844,3.559 6.205,3.881 6.476,4.292C6.747,4.703 6.882,5.155 6.882,5.65C6.882,6.145 6.758,6.574 6.511,6.938C6.264,7.302 5.939,7.577 5.538,7.764L5.538,7.848C6.042,8.025 6.45,8.319 6.763,8.73C7.076,9.141 7.232,9.621 7.232,10.172C7.232,10.723 7.09,11.213 6.805,11.642C6.52,12.071 6.138,12.405 5.657,12.643C5.176,12.881 4.651,13 4.082,13L0.148,13L0.148,2.976ZM3.844,7.176C4.292,7.176 4.654,7.036 4.929,6.756C5.204,6.476 5.342,6.154 5.342,5.79C5.342,5.426 5.209,5.106 4.943,4.831C4.677,4.556 4.329,4.418 3.9,4.418L1.716,4.418L1.716,7.176L3.844,7.176ZM4.082,11.544C4.558,11.544 4.938,11.395 5.223,11.096C5.508,10.797 5.65,10.452 5.65,10.06C5.65,9.659 5.503,9.311 5.209,9.017C4.915,8.723 4.521,8.576 4.026,8.576L1.716,8.576L1.716,11.544L4.082,11.544ZM9.461,12.447C9.008,11.929 8.782,11.208 8.782,10.284L8.782,5.86L10.322,5.86L10.322,10.074C10.322,10.653 10.46,11.087 10.735,11.376C11.01,11.665 11.372,11.81 11.82,11.81C12.184,11.81 12.506,11.714 12.786,11.523C13.066,11.332 13.281,11.077 13.43,10.76C13.579,10.443 13.654,10.102 13.654,9.738L13.654,5.86L15.194,5.86L15.194,13L13.738,13L13.738,12.076L13.654,12.076C13.458,12.412 13.155,12.687 12.744,12.902C12.333,13.117 11.899,13.224 11.442,13.224C10.574,13.224 9.914,12.965 9.461,12.447ZM19.32,12.608L16.352,5.86L18.074,5.86L20.09,10.718L20.146,10.718L22.106,5.86L23.8,5.86L19.39,16.024L17.766,16.024L19.32,12.608ZM27.586,5.86L29.252,5.86L30.694,10.97L30.75,10.97L32.36,5.86L33.942,5.86L35.538,10.97L35.594,10.97L37.036,5.86L38.674,5.86L36.392,13L34.768,13L33.13,7.876L33.088,7.876L31.464,13L29.868,13L27.586,5.86ZM39.965,4.523C39.764,4.322 39.664,4.077 39.664,3.788C39.664,3.499 39.764,3.254 39.965,3.053C40.166,2.852 40.411,2.752 40.7,2.752C40.989,2.752 41.234,2.852 41.435,3.053C41.636,3.254 41.736,3.499 41.736,3.788C41.736,4.077 41.636,4.322 41.435,4.523C41.234,4.724 40.989,4.824 40.7,4.824C40.411,4.824 40.166,4.724 39.965,4.523ZM39.93,5.86L41.47,5.86L41.47,13L39.93,13L39.93,5.86ZM45.498,12.958C45.218,12.855 44.989,12.72 44.812,12.552C44.411,12.151 44.21,11.605 44.21,10.914L44.21,7.218L42.964,7.218L42.964,5.86L44.21,5.86L44.21,3.844L45.75,3.844L45.75,5.86L47.486,5.86L47.486,7.218L45.75,7.218L45.75,10.578C45.75,10.961 45.825,11.231 45.974,11.39C46.114,11.577 46.357,11.67 46.702,11.67C46.861,11.67 47.001,11.649 47.122,11.607C47.243,11.565 47.374,11.497 47.514,11.404L47.514,12.902C47.206,13.042 46.833,13.112 46.394,13.112C46.077,13.112 45.778,13.061 45.498,12.958ZM49.176,2.976L50.716,2.976L50.716,5.706L50.646,6.798L50.716,6.798C50.921,6.462 51.227,6.184 51.633,5.965C52.039,5.746 52.475,5.636 52.942,5.636C53.81,5.636 54.473,5.89 54.93,6.399C55.387,6.908 55.616,7.601 55.616,8.478L55.616,13L54.076,13L54.076,8.688C54.076,8.147 53.934,7.741 53.649,7.47C53.364,7.199 52.993,7.064 52.536,7.064C52.191,7.064 51.88,7.162 51.605,7.358C51.33,7.554 51.113,7.813 50.954,8.135C50.795,8.457 50.716,8.8 50.716,9.164L50.716,13L49.176,13L49.176,2.976Z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FFFFFF"
android:strokeWidth="1"/>
<path
android:pathData="M81.526,2.635L81.526,6.718L84.044,6.718C84.644,6.718 85.14,6.516 85.532,6.113C85.935,5.711 86.137,5.231 86.137,4.676C86.137,4.132 85.935,3.658 85.532,3.254C85.14,2.841 84.644,2.634 84.044,2.634L81.526,2.634L81.526,2.635ZM81.526,8.155L81.526,12.891L80.022,12.891L80.022,1.198L84.011,1.198C85.025,1.198 85.885,1.535 86.594,2.21C87.314,2.885 87.674,3.707 87.674,4.676C87.674,5.667 87.314,6.495 86.594,7.158C85.897,7.823 85.035,8.154 84.011,8.154L81.526,8.154L81.526,8.155Z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FFFFFF"
android:strokeWidth="1"/>
<path
android:pathData="M89.194,10.442C89.194,10.834 89.36,11.16 89.693,11.422C90.025,11.683 90.415,11.813 90.861,11.813C91.494,11.813 92.057,11.579 92.553,11.112C93.05,10.643 93.297,10.093 93.297,9.463C92.828,9.092 92.174,8.907 91.335,8.907C90.724,8.907 90.215,9.055 89.807,9.349C89.398,9.643 89.194,10.006 89.194,10.442M91.14,4.627C92.252,4.627 93.129,4.924 93.773,5.518C94.415,6.111 94.737,6.925 94.737,7.959L94.737,12.891L93.298,12.891L93.298,11.781L93.233,11.781C92.611,12.695 91.783,13.153 90.747,13.153C89.865,13.153 89.126,12.891 88.532,12.369C87.938,11.846 87.641,11.193 87.641,10.409C87.641,9.581 87.954,8.923 88.581,8.433C89.208,7.943 90.044,7.698 91.09,7.698C91.983,7.698 92.72,7.861 93.297,8.188L93.297,7.844C93.297,7.322 93.09,6.878 92.676,6.513C92.261,6.149 91.777,5.967 91.221,5.967C90.381,5.967 89.717,6.32 89.226,7.029L87.902,6.195C88.632,5.15 89.711,4.627 91.14,4.627"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FFFFFF"
android:strokeWidth="1"/>
<path
android:pathData="M102.993,4.889l-5.02,11.531l-1.553,0l1.864,-4.035l-3.303,-7.496l1.635,0l2.387,5.749l0.033,0l2.322,-5.749z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FFFFFF"
android:strokeWidth="1"/>
<path
android:pathData="M75.448,7.134C75.448,6.661 75.408,6.205 75.332,5.768L68.988,5.768L68.988,8.356L72.622,8.356C72.466,9.199 71.994,9.917 71.278,10.398L71.278,12.079L73.447,12.079C74.716,10.908 75.448,9.179 75.448,7.134"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#4285F4"
android:strokeWidth="1"/>
<path
android:pathData="M68.988,13.701C70.804,13.701 72.332,13.105 73.447,12.079L71.278,10.398C70.675,10.804 69.897,11.041 68.988,11.041C67.234,11.041 65.744,9.859 65.212,8.267L62.978,8.267L62.978,9.998C64.085,12.193 66.36,13.701 68.988,13.701"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#34A853"
android:strokeWidth="1"/>
<path
android:pathData="M65.212,8.267C65.076,7.861 65.001,7.428 65.001,6.981C65.001,6.534 65.076,6.101 65.212,5.695L65.212,3.964L62.978,3.964C62.52,4.871 62.261,5.896 62.261,6.981C62.261,8.066 62.52,9.091 62.978,9.998L65.212,8.267Z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FABB05"
android:strokeWidth="1"/>
<path
android:pathData="M68.988,2.921C69.98,2.921 70.868,3.262 71.569,3.929L71.569,3.93L73.489,2.012C72.323,0.928 70.803,0.261 68.988,0.261C66.36,0.261 64.085,1.769 62.978,3.964L65.212,5.695C65.744,4.103 67.234,2.921 68.988,2.921"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#E94235"
android:strokeWidth="1"/>
</vector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@drawable/googlepay_button_background_image" />
</selector>

View file

@ -0,0 +1,48 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="41dp"
android:height="17dp"
android:viewportWidth="41.0"
android:viewportHeight="17.0">
<path
android:pathData="M19.526,2.635L19.526,6.718L22.044,6.718C22.644,6.718 23.14,6.516 23.532,6.113C23.935,5.711 24.137,5.231 24.137,4.676C24.137,4.132 23.935,3.658 23.532,3.254C23.14,2.841 22.644,2.634 22.044,2.634L19.526,2.634L19.526,2.635ZM19.526,8.155L19.526,12.891L18.022,12.891L18.022,1.198L22.011,1.198C23.025,1.198 23.885,1.535 24.594,2.21C25.314,2.885 25.674,3.707 25.674,4.676C25.674,5.667 25.314,6.495 24.594,7.158C23.897,7.823 23.035,8.154 22.011,8.154L19.526,8.154L19.526,8.155Z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FFFFFF"
android:strokeWidth="1"/>
<path
android:pathData="M27.194,10.442C27.194,10.834 27.36,11.16 27.693,11.422C28.025,11.683 28.415,11.813 28.861,11.813C29.494,11.813 30.057,11.579 30.553,11.112C31.05,10.643 31.297,10.093 31.297,9.463C30.828,9.092 30.174,8.907 29.335,8.907C28.724,8.907 28.215,9.055 27.807,9.349C27.398,9.643 27.194,10.006 27.194,10.442M29.14,4.627C30.252,4.627 31.129,4.924 31.773,5.518C32.415,6.111 32.737,6.925 32.737,7.959L32.737,12.891L31.298,12.891L31.298,11.781L31.233,11.781C30.611,12.695 29.783,13.153 28.747,13.153C27.865,13.153 27.126,12.891 26.532,12.369C25.938,11.846 25.641,11.193 25.641,10.409C25.641,9.581 25.954,8.923 26.581,8.433C27.208,7.943 28.044,7.698 29.09,7.698C29.983,7.698 30.72,7.861 31.297,8.188L31.297,7.844C31.297,7.322 31.09,6.878 30.676,6.513C30.261,6.149 29.777,5.967 29.221,5.967C28.381,5.967 27.717,6.32 27.226,7.029L25.902,6.195C26.632,5.15 27.711,4.627 29.14,4.627"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FFFFFF"
android:strokeWidth="1"/>
<path
android:pathData="M40.993,4.889l-5.02,11.531l-1.553,0l1.864,-4.035l-3.303,-7.496l1.635,0l2.387,5.749l0.033,0l2.322,-5.749z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FFFFFF"
android:strokeWidth="1"/>
<path
android:pathData="M13.448,7.134C13.448,6.661 13.408,6.205 13.332,5.768L6.988,5.768L6.988,8.356L10.622,8.356C10.466,9.199 9.994,9.917 9.278,10.398L9.278,12.079L11.447,12.079C12.716,10.908 13.448,9.179 13.448,7.134"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#4285F4"
android:strokeWidth="1"/>
<path
android:pathData="M6.988,13.701C8.804,13.701 10.332,13.105 11.447,12.079L9.278,10.398C8.675,10.804 7.897,11.041 6.988,11.041C5.234,11.041 3.744,9.859 3.212,8.267L0.978,8.267L0.978,9.998C2.085,12.193 4.36,13.701 6.988,13.701"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#34A853"
android:strokeWidth="1"/>
<path
android:pathData="M3.212,8.267C3.076,7.861 3.001,7.428 3.001,6.981C3.001,6.534 3.076,6.101 3.212,5.695L3.212,3.964L0.978,3.964C0.52,4.871 0.261,5.896 0.261,6.981C0.261,8.066 0.52,9.091 0.978,9.998L3.212,8.267Z"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#FABB05"
android:strokeWidth="1"/>
<path
android:pathData="M6.988,2.921C7.98,2.921 8.868,3.262 9.569,3.929L9.569,3.93L11.489,2.012C10.323,0.928 8.803,0.261 6.988,0.261C4.36,0.261 2.085,1.769 0.978,3.964L3.212,5.695C3.744,4.103 5.234,2.921 6.988,2.921"
android:strokeColor="#00000000"
android:fillType="evenOdd"
android:fillColor="#E94235"
android:strokeWidth="1"/>
</vector>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@drawable/googlepay_button_no_shadow_background_image" />
</selector>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_enabled="false">
<shape
android:shape="rectangle" >
<corners android:radius="4dp"/>
<solid android:color="#7FFFFFFF"/>
</shape>
</item>
<item android:drawable="@android:color/transparent" />
</selector>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="@color/iconGray" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector android:autoMirrored="true" android:height="20dp"
android:viewportHeight="20" android:viewportWidth="20"
android:width="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
<group>
<clip-path android:pathData="M0,0h20v20h-20z"/>
<path android:fillColor="#00000000"
android:pathData="M17.2412,14.1916L17.2412,11.7284C16.8163,11.6812 16.4151,11.516 16.0847,11.1855C15.3295,10.4303 15.3531,9.2266 16.0847,8.495C16.4151,8.1646 16.8163,7.9994 17.2412,7.9522L17.2412,5.4889C17.2412,5.1585 16.958,4.8753 16.6275,4.8753L3.4197,4.8281C3.0893,4.8281 2.806,5.1113 2.806,5.4417L2.806,7.905C3.2309,7.9522 3.6793,8.1174 4.0097,8.4478C4.7649,9.203 4.7413,10.4067 4.0097,11.1383C3.6793,11.4688 3.2545,11.6576 2.806,11.6812L2.806,14.1444C2.806,14.4748 3.0893,14.7581 3.4197,14.7581L16.6275,14.7581C16.958,14.8053 17.2412,14.522 17.2412,14.1916Z"
android:strokeColor="#090E13" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1.1"/>
</group>
</vector>

View file

@ -0,0 +1,11 @@
<vector android:autoMirrored="true" android:height="20dp"
android:viewportHeight="20" android:viewportWidth="20"
android:width="20dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00000000"
android:pathData="M2.4,6H14.4V17.2H2.4V6ZM17.6,2.8L14.4,6H2.4L6.5852,2.8H17.6Z"
android:strokeColor="#000000" android:strokeLineJoin="round" android:strokeWidth="1.1"/>
<path android:fillColor="#00000000"
android:pathData="M8.4,6L12,2.8M17.6,13.2L14.4,17.2V6L17.6,2.8V13.2ZM6.4,8.4H10.4H6.4Z"
android:strokeColor="#000000" android:strokeLineCap="round"
android:strokeLineJoin="round" android:strokeWidth="1.1"/>
</vector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape android:shape="line">
<stroke android:width="1dp" android:color="@color/darkGray6"/>
</shape>
</item>
</selector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/backgroundGray" />
<corners android:radius="4dp" />
</shape>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="48sp"
android:background="@drawable/googlepay_button_no_shadow_background"
android:padding="2sp"
android:contentDescription="@string/buy_with_googlepay_button_content_description">
<LinearLayout
android:duplicateParentState="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="2"
android:gravity="center_vertical"
android:orientation="vertical">
<ImageView
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:scaleType="fitCenter"
android:duplicateParentState="true"
android:src="@drawable/buy_with_googlepay_button_content"/>
</LinearLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:duplicateParentState="true"
android:src="@drawable/googlepay_button_overlay"/>
</RelativeLayout>

View file

@ -0,0 +1,375 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator_details_confirm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLightGray"
android:clipChildren="false"
android:clipToPadding="false"
android:fitsSystemWindows="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
style="@style/ThemeOverlay.MyTheme.Toolbar.AccentColorMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backgroundLightGray"
android:fitsSystemWindows="true"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_clear_24"
app:title="@string/shop_title"
app:titleCentered="true" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:clickable="true"
android:fitsSystemWindows="true"
android:focusable="true"
android:focusableInTouchMode="true">
<FrameLayout
android:id="@+id/fl_cards"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="36dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="36dp"
android:layout_weight="1"
app:layout_constraintBottom_toTopOf="@+id/tv_header"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/imv_third"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/card_new"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/tv_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/darkGray2" />
<ImageView
android:id="@+id/imv_second"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/card_new"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/tv_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/darkGray4" />
<ImageView
android:id="@+id/imv_first"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/card_new"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/tv_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
<TextView
android:id="@+id/tv_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="44dp"
android:layout_marginBottom="14dp"
android:fontFamily="sans-serif-medium"
android:text="@string/shop_one_wallet"
android:textColor="#060606"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@+id/chip_group_product"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/fl_cards" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group_product"
android:layout_width="wrap_content"
android:layout_height="42dp"
android:layout_marginBottom="24dp"
android:background="@drawable/shape_rectangle_rounded_100"
android:backgroundTint="@color/backgroundGray"
android:paddingStart="3dp"
android:paddingTop="3dp"
android:paddingEnd="3dp"
app:layout_constraintBottom_toTopOf="@id/v_shipping_discount_background"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
android:id="@+id/chip_product_1"
style="@style/ShopChips"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:checked="true"
android:text="@string/shop_3_cards"
app:chipMinTouchTargetSize="0dp" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_product_2"
style="@style/ShopChips"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:checked="false"
android:text="@string/shop_2_cards"
app:chipMinTouchTargetSize="0dp" />
</com.google.android.material.chip.ChipGroup>
<View
android:id="@+id/v_shipping_discount_background"
android:layout_width="match_parent"
android:layout_height="93dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/shape_rectangle_rounded_4"
app:layout_constraintBottom_toTopOf="@id/v_total_background" />
<ImageView
android:id="@+id/iv_delivery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:src="@drawable/ic_shipping"
app:layout_constraintBottom_toTopOf="@id/v_divider"
app:layout_constraintStart_toStartOf="@id/v_shipping_discount_background"
app:layout_constraintTop_toTopOf="@id/v_shipping_discount_background" />
<TextView
android:id="@+id/tv_delivery_worldwide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:text="@string/shop_shipping"
android:textColor="@color/textBlack"
android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@id/v_divider"
app:layout_constraintStart_toEndOf="@id/iv_delivery"
app:layout_constraintTop_toTopOf="@id/v_shipping_discount_background" />
<TextView
android:id="@+id/tv_delivery_cost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/shop_free"
android:textColor="@color/textBlack"
android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@id/v_divider"
app:layout_constraintEnd_toEndOf="@id/v_shipping_discount_background"
app:layout_constraintTop_toTopOf="@id/v_shipping_discount_background" />
<View
android:id="@+id/v_divider"
android:layout_width="0dp"
android:layout_height="0.5dp"
android:background="#BEBEBE"
app:layout_constraintBottom_toBottomOf="@id/v_shipping_discount_background"
app:layout_constraintEnd_toEndOf="@id/v_shipping_discount_background"
app:layout_constraintStart_toStartOf="@id/v_shipping_discount_background"
app:layout_constraintTop_toTopOf="@id/v_shipping_discount_background" />
<ImageView
android:id="@+id/iv_promo_code"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_promo_code"
app:layout_constraintBottom_toBottomOf="@id/v_shipping_discount_background"
app:layout_constraintStart_toStartOf="@id/v_shipping_discount_background"
app:layout_constraintTop_toBottomOf="@id/v_divider" />
<EditText
android:id="@+id/et_promo_code"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:hint="@string/shop_i_have_a_promo_code"
android:imeOptions="actionDone"
android:inputType="textCapCharacters"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:singleLine="true"
android:textColor="@color/textBlack"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@id/v_shipping_discount_background"
app:layout_constraintEnd_toEndOf="@id/v_shipping_discount_background"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/iv_promo_code"
app:layout_constraintTop_toBottomOf="@id/v_divider" />
<ProgressBar
android:id="@+id/pb_promo_code"
android:layout_width="wrap_content"
android:layout_height="28dp"
android:layout_marginEnd="16dp"
android:indeterminateTint="@color/darkGray2"
app:layout_constraintBottom_toBottomOf="@id/v_shipping_discount_background"
app:layout_constraintEnd_toEndOf="@id/v_shipping_discount_background"
app:layout_constraintTop_toBottomOf="@id/v_divider" />
<View
android:id="@+id/v_total_background"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="21dp"
android:background="@drawable/shape_rectangle_rounded_4"
app:layout_constraintBottom_toTopOf="@id/ll_buttons" />
<TextView
android:id="@+id/tv_total_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/shop_total"
android:textColor="@color/textBlack"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@id/v_total_background"
app:layout_constraintStart_toStartOf="@id/v_total_background"
app:layout_constraintTop_toTopOf="@id/v_total_background" />
<TextView
android:id="@+id/tv_total_before_discount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:background="@drawable/shape_line"
android:textColor="@color/darkGray6"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="@id/v_total_background"
app:layout_constraintEnd_toStartOf="@id/tv_total"
app:layout_constraintTop_toTopOf="@id/v_total_background"
tools:text="$44.20" />
<TextView
android:id="@+id/tv_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="6dp"
android:paddingEnd="12dp"
android:textColor="@color/darkGray6"
android:textSize="22sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/v_total_background"
app:layout_constraintEnd_toEndOf="@id/v_total_background"
app:layout_constraintTop_toTopOf="@id/v_total_background"
tools:text="$44.20" />
<ProgressBar
android:id="@+id/pb_price"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_marginEnd="16dp"
android:indeterminateTint="@color/darkGray2"
app:layout_constraintBottom_toBottomOf="@id/v_total_background"
app:layout_constraintEnd_toEndOf="@id/v_total_background"
app:layout_constraintTop_toTopOf="@id/v_total_background" />
<LinearLayout
android:id="@+id/ll_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent">
<include
android:id="@+id/btn_pay_google_pay"
layout="@layout/buy_with_googlepay_button"
android:layout_width="match_parent"
android:layout_height="48sp"
android:layout_gravity="bottom"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_main_action"
style="@style/TapBlackButton"
android:layout_width="match_parent"
android:layout_height="48sp"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:layout_marginBottom="70dp"
android:text="@string/shop_buy_now"
android:textAlignment="center"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_alternative_payment"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="match_parent"
android:layout_height="42dp"
android:layout_gravity="bottom"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:text="@string/shop_other_payment_methods"
android:textAllCaps="false"
android:textColor="@color/textBlack"
android:textSize="16sp"
android:visibility="gone" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_2_cards"
style="@style/TapButton"
android:text="2 cards"
app:layout_constraintEnd_toStartOf="@+id/btn_3_cards"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="16dp"/>
<Button
android:id="@+id/btn_3_cards"
style="@style/TapButton"
android:text="3 cards"
android:layout_margin="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btn_2_cards"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_promo_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:hint="Promo Code"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:textAlignment="center"/>
<TextView
android:id="@+id/tv_price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:text="1.00 USD"
app:layout_constraintBottom_toTopOf="@id/tv_total"
android:textAlignment="center"
android:padding="16dp"/>
<TextView
android:id="@+id/tv_total"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:text="1.00 USD"
app:layout_constraintBottom_toTopOf="@id/pay_gpay"
android:textAlignment="center"
android:padding="16dp"/>
<Button
android:id="@+id/pay_gpay"
style="@style/TapButton"
android:text="GPay"
app:layout_constraintEnd_toStartOf="@+id/pay_web"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_margin="16dp"/>
<Button
android:id="@+id/pay_web"
style="@style/TapButton"
android:text="Pay online"
android:layout_margin="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/pay_gpay"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -29,6 +29,11 @@
<color name="buttonGray">#F4F5F6</color>
<color name="textGray">#DE000000</color>
<color name="textBlack">#060606</color>
<color name="iconGray">#686868</color>
<color name="backgroundGray">#F2F2F2</color>
<color name="chipBlack">#090E13</color>
<color name="twins_dark">#14181D</color>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="googlepay_button_content_description">Google Pay</string>
<string name="buy_with_googlepay_button_content_description">Buy with Google Pay</string>
<string name="donate_with_googlepay_button_content_description">Donate With Google Pay</string>
<string name="pay_with_googlepay_button_content_description">Pay With Google Pay</string>
<string name="subscribe_with_googlepay_button_content_description">Subscribe With Google Pay</string>
<string name="book_with_googlepay_button_content_description">Book With Google Pay</string>
<string name="checkout_with_googlepay_button_content_description">Checkout With Google Pay</string>
<string name="order_with_googlepay_button_content_description">Order With Google Pay</string>
<string name="view_in_googlepay_button_content_description">View In Google Pay</string>
</resources>

View file

@ -317,4 +317,16 @@ Amount to pay: %s</string>
<string name="token_symbol_address_format" translatable="false">%1s (%2s)</string>
<string name="shop_one_wallet" translatable="false">One Wallet</string>
<string name="shop_3_cards" translatable="false">3 cards</string>
<string name="shop_2_cards" translatable="false">2 cards</string>
<string name="shop_shipping" translatable="false">Shipping</string>
<string name="shop_free" translatable="false">Free</string>
<string name="shop_i_have_a_promo_code" translatable="false">I have a promo code...</string>
<string name="shop_total" translatable="false">Total</string>
<string name="shop_other_payment_methods" translatable="false">Other payment methods</string>
<string name="shop_buy_now" translatable="false">Buy now</string>
<string name="shop_title" translatable="false">Order card</string>
<string name="shop_free_delivery" translatable="false">Delivery (Free shipping)</string>
</resources>

View file

@ -147,6 +147,15 @@
<item name="android:windowSoftInputMode">adjustResize</item>
</style>
<style name="ShopChips" parent="@style/Widget.MaterialComponents.Chip.Choice">
<item name="chipBackgroundColor">@color/selector_chip_shop</item>
<item name="android:minWidth">100dp</item>
<item name="android:textAlignment">center</item>
<item name="android:textSize">15sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/selector_chip_shop_text</item>
</style>
</resources>
<!-- android:fontFamily="sans-serif" // roboto regular -->