Updated on 2026-08-14
This commit is contained in:
parent
2105360004
commit
d76fcd143c
24 changed files with 645 additions and 5 deletions
|
|
@ -310,4 +310,8 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
<queries>
|
||||
<!-- Needed from Android 11 to open Google Wallet for payment with Visa card -->
|
||||
<package android:name="com.google.android.apps.walletnfcrel" />
|
||||
</queries>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ package com.tangem.tap.data
|
|||
import android.content.Context
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
|
|
@ -21,6 +25,7 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
@ApplicationContext applicationContext: Context,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : TangemPayStorage {
|
||||
|
||||
private val secureStorage by lazy {
|
||||
|
|
@ -75,6 +80,20 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
|
||||
}
|
||||
|
||||
override suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.getSyncOrNull(
|
||||
key = PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress),
|
||||
) == true
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), isDone)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||
}
|
||||
|
|
@ -84,6 +103,7 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
secureStorage.delete(createCustomerAddressKey(userWalletId))
|
||||
secureStorage.delete(createKey(customerWalletAddress))
|
||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false)
|
||||
}
|
||||
|
||||
private fun createCustomerAddressKey(userWalletId: UserWalletId): String = userWalletId.stringValue
|
||||
|
|
|
|||
|
|
@ -181,6 +181,9 @@ object PreferencesKeys {
|
|||
fun getHotWalletUnlockDeadlineKey(attemptId: String) =
|
||||
longPreferencesKey(name = "hotWalletUnlockDeadline_$attemptId")
|
||||
|
||||
fun getTangemPayAddToWalletKey(customerWalletAddress: String) =
|
||||
booleanPreferencesKey("tangem_pay_add_to_wallet_done_key_$customerWalletAddress")
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,5 +18,9 @@ interface TangemPayStorage {
|
|||
|
||||
suspend fun clearOrderId(customerWalletAddress: String)
|
||||
|
||||
suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean
|
||||
|
||||
suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean)
|
||||
|
||||
suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||
}
|
||||
|
|
@ -1523,9 +1523,11 @@
|
|||
<string name="yield_module_stop_earning_sheet_fee_note">Les frais de réseau seront déduits du montant que vous retirez.</string>
|
||||
<string name="yield_module_supply_apr">Rendement annuel brut (APY)</string>
|
||||
<string name="yield_module_token_details_earn_notification_apy">APY</string>
|
||||
<string name="yield_module_token_details_earn_notification_description">Les intérêts sont cumulés automatiquement.</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_subtitle">Les intérêts sont cumulés automatiquement.</string>
|
||||
<string name="yield_module_token_details_earn_notification_earning_on_your_balance_title">Mode de rendement</string>
|
||||
<string name="yield_module_token_details_earn_notification_processing">Traitement de votre dépôt</string>
|
||||
<string name="yield_module_token_details_earn_notification_title">Mode Rendement</string>
|
||||
<string name="yield_module_transfer_mode_automatic">Automatique</string>
|
||||
<string name="yield_module_unable_to_cover_fee_description">Déposez %1$s %2$s pour couvrir les frais de réseau liés aux transactions.</string>
|
||||
<string name="yield_module_unable_to_cover_fee_title">Impossible de couvrir les frais %s</string>
|
||||
|
|
|
|||
|
|
@ -1338,6 +1338,15 @@
|
|||
<string name="tangempay_card_details_error_text">Failed to load data. Try again later.</string>
|
||||
<string name="tangempay_card_details_freeze_card">Freeze Card</string>
|
||||
<string name="tangempay_card_details_hide_text">Hide</string>
|
||||
<string name="tangempay_card_details_open_wallet_button">Open Google Wallet</string>
|
||||
<string name="tangempay_card_details_open_wallet_notification_subtitle">Set up Tangem Pay in a few taps and start paying with Google Pay.</string>
|
||||
<string name="tangempay_card_details_open_wallet_notification_title">Add your card to Google Pay</string>
|
||||
<string name="tangempay_card_details_open_wallet_step_1">Open Google Wallet</string>
|
||||
<string name="tangempay_card_details_open_wallet_step_2">Tap “Add a card”</string>
|
||||
<string name="tangempay_card_details_open_wallet_step_3">Enter the card details manually</string>
|
||||
<string name="tangempay_card_details_open_wallet_step_4">Verify card using the OTP sent to your device.</string>
|
||||
<string name="tangempay_card_details_open_wallet_step_5">All set! Your card is ready to use.</string>
|
||||
<string name="tangempay_card_details_open_wallet_title">Add card to Google Pay</string>
|
||||
<string name="tangempay_card_details_receive_error_description">Technical issues detected. Please try again later or contact support.</string>
|
||||
<string name="tangempay_card_details_receive_error_title">Receive unavailable now</string>
|
||||
<string name="tangempay_card_details_reveal_text">Reveal</string>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
|||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.pay.models.request.CardDetailsRequest
|
||||
import com.tangem.datasource.api.pay.models.request.SetPinRequest
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
import com.tangem.domain.pay.model.SetPinResult
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
import com.tangem.domain.pay.model.TangemPayCardDetails
|
||||
|
|
@ -24,6 +25,7 @@ internal class DefaultCardDetailsRepository @Inject constructor(
|
|||
private val visaLibLoader: VisaLibLoader,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
private val rainCryptoUtil: RainCryptoUtil,
|
||||
private val storage: TangemPayStorage,
|
||||
) : CardDetailsRepository {
|
||||
|
||||
override suspend fun getCardBalance(): Either<UniversalError, TangemPayCardBalance> {
|
||||
|
|
@ -98,6 +100,18 @@ internal class DefaultCardDetailsRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun isAddToWalletDone(): Either<UniversalError, Boolean> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
storage.getAddToWalletDone(requestHelper.getCustomerWalletAddress())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setAddToWalletAsDone(): Either<UniversalError, Unit> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
storage.storeAddToWalletDone(requestHelper.getCustomerWalletAddress(), isDone = true)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getPublicKeyBase64(): String {
|
||||
val env = apiConfigsManager.getEnvironmentConfig(ApiConfig.ID.TangemPay).environment
|
||||
return when (env) {
|
||||
|
|
|
|||
|
|
@ -13,4 +13,8 @@ interface CardDetailsRepository {
|
|||
suspend fun revealCardDetails(): Either<UniversalError, TangemPayCardDetails>
|
||||
|
||||
suspend fun setPin(pin: String): Either<UniversalError, SetPinResult>
|
||||
|
||||
suspend fun isAddToWalletDone(): Either<UniversalError, Boolean>
|
||||
|
||||
suspend fun setAddToWalletAsDone(): Either<UniversalError, Unit>
|
||||
}
|
||||
|
|
@ -67,6 +67,10 @@ class DefaultTangemPayDetailsContainerComponent @AssistedInject constructor(
|
|||
TangemPayDetailsInnerRoute.ChangePINSuccess -> TangemPayChangePinSuccessComponent(
|
||||
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),
|
||||
)
|
||||
TangemPayDetailsInnerRoute.AddToWallet -> TangemPayAddToWalletComponent(
|
||||
appComponentContext = childByContext(componentContext = componentContext, router = innerRouter),
|
||||
params = params,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onChildBack() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.features.tangempay.components
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.tangempay.components.cardDetails.DefaultTangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.model.TangemPayAddToWalletModel
|
||||
import com.tangem.features.tangempay.ui.TangemPayAddToWalletScreen
|
||||
|
||||
internal class TangemPayAddToWalletComponent(
|
||||
private val appComponentContext: AppComponentContext,
|
||||
private val params: TangemPayDetailsContainerComponent.Params,
|
||||
) : AppComponentContext by appComponentContext, ComposableContentComponent {
|
||||
|
||||
private val model: TangemPayAddToWalletModel = getOrCreateModel()
|
||||
|
||||
private val cardDetailsBlockComponent = DefaultTangemPayCardDetailsBlockComponent(
|
||||
appComponentContext = child("cardDetailsBlockComponent"),
|
||||
params = TangemPayCardDetailsBlockComponent.Params(params = params),
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
BackHandler(onBack = router::pop)
|
||||
TangemPayAddToWalletScreen(
|
||||
state = state,
|
||||
cardDetailsBlockComponent = cardDetailsBlockComponent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.tangempay.di
|
|||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.tangempay.model.TangemPayAddToWalletModel
|
||||
import com.tangem.features.tangempay.model.TangemPayCardDetailsBlockModel
|
||||
import com.tangem.features.tangempay.model.TangemPayChangePinModel
|
||||
import com.tangem.features.tangempay.model.TangemPayDetailsModel
|
||||
|
|
@ -41,4 +42,9 @@ internal interface TangemPayModelModule {
|
|||
@IntoMap
|
||||
@ClassKey(TangemPayCardDetailsBlockModel::class)
|
||||
fun bindTangemPayCardDetailsBlockModel(model: TangemPayCardDetailsBlockModel): Model
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(TangemPayAddToWalletModel::class)
|
||||
fun bindTangemPayAddToWalletModel(model: TangemPayAddToWalletModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.features.tangempay.entity
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class TangemPayAddToWalletUM(
|
||||
val steps: ImmutableList<TangemPayAddToWalletStepItemUM>,
|
||||
val showAddToWalletButton: Boolean,
|
||||
val onBackClick: () -> Unit,
|
||||
val onClickOpenWallet: () -> Unit,
|
||||
)
|
||||
|
||||
internal data class TangemPayAddToWalletStepItemUM(
|
||||
val count: Int,
|
||||
val text: TextReference,
|
||||
)
|
||||
|
|
@ -45,6 +45,7 @@ internal class TangemPayDetailsStateFactory(
|
|||
),
|
||||
),
|
||||
),
|
||||
addToWalletBlockState = null,
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ internal data class TangemPayDetailsUM(
|
|||
val topBarConfig: TangemPayDetailsTopBarConfig,
|
||||
val pullToRefreshConfig: PullToRefreshConfig,
|
||||
val balanceBlockState: TangemPayDetailsBalanceBlockState,
|
||||
val addToWalletBlockState: AddToWalletBlockState?,
|
||||
val isBalanceHidden: Boolean,
|
||||
)
|
||||
|
||||
|
|
@ -41,4 +42,9 @@ internal sealed class TangemPayDetailsBalanceBlockState {
|
|||
data class Error(
|
||||
override val actionButtons: ImmutableList<ActionButtonConfig>,
|
||||
) : TangemPayDetailsBalanceBlockState()
|
||||
}
|
||||
}
|
||||
|
||||
internal data class AddToWalletBlockState(
|
||||
val onClick: () -> Unit,
|
||||
val onClickClose: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.features.tangempay.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayAddToWalletStepItemUM
|
||||
import com.tangem.features.tangempay.entity.TangemPayAddToWalletUM
|
||||
import com.tangem.features.tangempay.utils.GoogleWalletUtil
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class TangemPayAddToWalletModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val googleWalletUtil: GoogleWalletUtil,
|
||||
) : Model() {
|
||||
|
||||
val uiState: StateFlow<TangemPayAddToWalletUM>
|
||||
field = MutableStateFlow(getInitialState())
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getInitialState(): TangemPayAddToWalletUM {
|
||||
return TangemPayAddToWalletUM(
|
||||
steps = persistentListOf(
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 1,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_1),
|
||||
),
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 2,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_2),
|
||||
),
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 3,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_3),
|
||||
),
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 4,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_4),
|
||||
),
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 5,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_5),
|
||||
),
|
||||
),
|
||||
showAddToWalletButton = googleWalletUtil.isWalletAvailable(),
|
||||
onBackClick = router::pop,
|
||||
onClickOpenWallet = googleWalletUtil::openWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import com.tangem.features.tangempay.entity.TangemPayDetailsStateFactory
|
|||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
import com.tangem.features.tangempay.model.listener.CardDetailsEvent
|
||||
import com.tangem.features.tangempay.model.listener.CardDetailsEventListener
|
||||
import com.tangem.features.tangempay.model.transformers.DetailsAddToWalletBannerTransformer
|
||||
import com.tangem.features.tangempay.model.transformers.DetailsBalanceTransformer
|
||||
import com.tangem.features.tangempay.model.transformers.TangemPayDetailsRefreshTransformer
|
||||
import com.tangem.features.tangempay.navigation.TangemPayDetailsInnerRoute
|
||||
|
|
@ -63,10 +64,12 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
|
||||
private val refreshStateJobHolder = JobHolder()
|
||||
private val fetchBalanceJobHolder = JobHolder()
|
||||
private val addToWalletBannerJobHolder = JobHolder()
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<TangemPayDetailsNavigation> = SlotNavigation()
|
||||
|
||||
init {
|
||||
fetchAddToWalletBanner()
|
||||
fetchBalance()
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +108,19 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
}.saveIn(fetchBalanceJobHolder)
|
||||
}
|
||||
|
||||
private fun fetchAddToWalletBanner() {
|
||||
modelScope.launch {
|
||||
val isDone = cardDetailsRepository.isAddToWalletDone().getOrNull() ?: false
|
||||
uiState.update(
|
||||
transformer = DetailsAddToWalletBannerTransformer(
|
||||
onClickBanner = ::onClickAddToWalletBlock,
|
||||
onClickCloseBanner = ::onClickCloseAddToWalletBlock,
|
||||
isDone = isDone,
|
||||
),
|
||||
)
|
||||
}.saveIn(addToWalletBannerJobHolder)
|
||||
}
|
||||
|
||||
private fun onRefreshSwipe(refreshState: ShowRefreshState) {
|
||||
modelScope.launch {
|
||||
cardDetailsEventListener.send(CardDetailsEvent.Hide)
|
||||
|
|
@ -114,6 +130,23 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
}.saveIn(refreshStateJobHolder)
|
||||
}
|
||||
|
||||
private fun onClickAddToWalletBlock() {
|
||||
router.push(TangemPayDetailsInnerRoute.AddToWallet)
|
||||
}
|
||||
|
||||
private fun onClickCloseAddToWalletBlock() {
|
||||
modelScope.launch {
|
||||
cardDetailsRepository.setAddToWalletAsDone()
|
||||
uiState.update(
|
||||
transformer = DetailsAddToWalletBannerTransformer(
|
||||
onClickBanner = ::onClickAddToWalletBlock,
|
||||
onClickCloseBanner = ::onClickCloseAddToWalletBlock,
|
||||
isDone = true,
|
||||
),
|
||||
)
|
||||
}.saveIn(addToWalletBannerJobHolder)
|
||||
}
|
||||
|
||||
override fun onTransactionClick(item: TangemPayTxHistoryItem) {
|
||||
bottomSheetNavigation.activate(TangemPayDetailsNavigation.TransactionDetails(item))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.features.tangempay.model.transformers
|
||||
|
||||
import com.tangem.features.tangempay.entity.AddToWalletBlockState
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class DetailsAddToWalletBannerTransformer(
|
||||
private val onClickBanner: () -> Unit,
|
||||
private val onClickCloseBanner: () -> Unit,
|
||||
private val isDone: Boolean,
|
||||
) : Transformer<TangemPayDetailsUM> {
|
||||
|
||||
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
|
||||
return prevState.copy(
|
||||
addToWalletBlockState = if (isDone) {
|
||||
null
|
||||
} else {
|
||||
AddToWalletBlockState(onClick = onClickBanner, onClickClose = onClickCloseBanner)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,4 +13,7 @@ internal sealed class TangemPayDetailsInnerRoute : Route {
|
|||
|
||||
@Serializable
|
||||
data object ChangePINSuccess : TangemPayDetailsInnerRoute()
|
||||
|
||||
@Serializable
|
||||
data object AddToWallet : TangemPayDetailsInnerRoute()
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.AddToWalletBlockState
|
||||
|
||||
private const val GRADIENT_START_COLOR = 0xFF252934
|
||||
private const val GRADIENT_END_COLOR = 0xFF12141E
|
||||
private const val GRADIENT_OFFSET_X = 0f
|
||||
private const val GRADIENT_OFFSET_Y = 80F
|
||||
private const val GRADIENT_RADIUS = 200F
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayAddToWalletBlock(state: AddToWalletBlockState, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(
|
||||
brush = Brush.radialGradient(
|
||||
colors = listOf(Color(GRADIENT_START_COLOR), Color(GRADIENT_END_COLOR)),
|
||||
center = Offset(GRADIENT_OFFSET_X, GRADIENT_OFFSET_Y),
|
||||
radius = GRADIENT_RADIUS,
|
||||
),
|
||||
)
|
||||
.clickable(onClick = state.onClick),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.img_google_wallet_48),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(36.dp).clip(CircleShape),
|
||||
)
|
||||
|
||||
Spacer(Modifier.width(12.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_card_details_open_wallet_notification_title),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.constantWhite,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(6.dp))
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.tangempay_card_details_open_wallet_notification_subtitle),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.clickable(onClick = state.onClickClose)
|
||||
.padding(4.dp)
|
||||
.size(12.dp),
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
colorFilter = ColorFilter.tint(TangemTheme.colors.icon.inactive),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewTangemPayAddToWalletBlock() {
|
||||
TangemThemePreview {
|
||||
TangemPayAddToWalletBlock(AddToWalletBlockState({}, {}))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.tangempay.components.cardDetails.PreviewTangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.entity.TangemPayAddToWalletStepItemUM
|
||||
import com.tangem.features.tangempay.entity.TangemPayAddToWalletUM
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayAddToWalletScreen(
|
||||
state: TangemPayAddToWalletUM,
|
||||
cardDetailsBlockComponent: TangemPayCardDetailsBlockComponent,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
val cardDetailsState by cardDetailsBlockComponent.state.collectAsStateWithLifecycle()
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.navigationBarsPadding(),
|
||||
) {
|
||||
AppBarWithBackButton(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
onBackClick = state.onBackClick,
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.weight(1f),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(bottom = TangemTheme.dimens.spacing16 + bottomBarHeight),
|
||||
) {
|
||||
item(key = TangemPayCardDetailsUM::class.java) {
|
||||
TangemPayCardDetailsBlockItem(component = cardDetailsBlockComponent, state = cardDetailsState)
|
||||
}
|
||||
|
||||
item(key = TangemPayAddToWalletUM::class.java) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 24.dp)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.tangempay_card_details_open_wallet_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
itemsIndexed(
|
||||
items = state.steps,
|
||||
key = { _, step -> "${TangemPayAddToWalletStepItemUM::class.java}${step.count}" },
|
||||
) { idx, step ->
|
||||
StepItem(
|
||||
modifier = Modifier.padding(bottom = if (idx < state.steps.lastIndex) 16.dp else 0.dp),
|
||||
stepNumber = step.count,
|
||||
title = step.text,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
text = resourceReference(R.string.common_got_it).resolveReference(),
|
||||
onClick = state.onBackClick,
|
||||
|
||||
)
|
||||
if (state.showAddToWalletButton) {
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_button).resolveReference(),
|
||||
onClick = state.onClickOpenWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StepItem(stepNumber: Int, title: TextReference, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.background(color = Color.Transparent, shape = TangemTheme.shapes.roundedCorners8)
|
||||
.padding(horizontal = 24.dp),
|
||||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.background(color = TangemColorPalette.Azure, shape = CircleShape),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stepNumber.toString(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun PreviewTangemPayAddToWalletScreen() {
|
||||
TangemThemePreview {
|
||||
TangemPayAddToWalletScreen(
|
||||
state = TangemPayAddToWalletUM(
|
||||
steps = persistentListOf(
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 1,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_1),
|
||||
),
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 2,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_2),
|
||||
),
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 3,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_3),
|
||||
),
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 4,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_4),
|
||||
),
|
||||
TangemPayAddToWalletStepItemUM(
|
||||
count = 5,
|
||||
text = resourceReference(R.string.tangempay_card_details_open_wallet_step_5),
|
||||
),
|
||||
),
|
||||
showAddToWalletButton = true,
|
||||
onBackClick = {},
|
||||
onClickOpenWallet = {},
|
||||
),
|
||||
cardDetailsBlockComponent = PreviewTangemPayCardDetailsBlockComponent(
|
||||
TangemPayCardDetailsUM(
|
||||
number = "•••• •••• •••• 1245",
|
||||
expiry = "••/••",
|
||||
cvv = "•••",
|
||||
onCopy = {},
|
||||
onClick = {},
|
||||
buttonText = TextReference.Res(R.string.tangempay_card_details_hide_text),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.features.tangempay.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.features.tangempay.components.cardDetails.TangemPayCardDetailsBlockComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayCardDetailsUM
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayCardDetailsBlockItem(
|
||||
component: TangemPayCardDetailsBlockComponent,
|
||||
state: TangemPayCardDetailsUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
component.CardDetailsBlockContent(state = state, modifier = modifier)
|
||||
}
|
||||
|
|
@ -90,10 +90,21 @@ internal fun TangemPayDetailsScreen(
|
|||
)
|
||||
},
|
||||
)
|
||||
with(cardDetailsBlockComponent) {
|
||||
item(TangemPayCardDetailsUM::class.java) {
|
||||
CardDetailsBlockContent(state = cardDetailsState)
|
||||
}
|
||||
if (state.addToWalletBlockState != null) {
|
||||
item(
|
||||
key = AddToWalletBlockState::class.java,
|
||||
content = {
|
||||
TangemPayAddToWalletBlock(
|
||||
state = state.addToWalletBlockState,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing12)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
item(TangemPayCardDetailsUM::class.java) {
|
||||
TangemPayCardDetailsBlockItem(component = cardDetailsBlockComponent, state = cardDetailsState)
|
||||
}
|
||||
with(txHistoryComponent) { txHistoryContent(listState = listState, state = txHistoryState) }
|
||||
}
|
||||
|
|
@ -300,12 +311,14 @@ private class TangemPayDetailsUMProvider : CollectionPreviewParameterProvider<Ta
|
|||
fiatBalance = "$1234.56",
|
||||
isBalanceFlickering = false,
|
||||
),
|
||||
addToWalletBlockState = AddToWalletBlockState({}, {}),
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
TangemPayDetailsUM(
|
||||
topBarConfig = TangemPayDetailsTopBarConfig(onBackClick = {}, items = null),
|
||||
pullToRefreshConfig = PullToRefreshConfig(isRefreshing = false, onRefresh = {}),
|
||||
balanceBlockState = TangemPayDetailsBalanceBlockState.Loading(actionButtons = persistentListOf()),
|
||||
addToWalletBlockState = AddToWalletBlockState({}, {}),
|
||||
isBalanceHidden = false,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.features.tangempay.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "GoogleWalletUtil"
|
||||
private const val WALLET_PACKAGE_NAME = "com.google.android.apps.walletnfcrel"
|
||||
|
||||
internal class GoogleWalletUtil @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
private var walletIntent: Intent? = null
|
||||
|
||||
fun isWalletAvailable(): Boolean = getWalletIntent() != null
|
||||
|
||||
fun openWallet() {
|
||||
val intent = getWalletIntent()
|
||||
if (intent != null) {
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWalletIntent(): Intent? {
|
||||
return if (walletIntent != null) {
|
||||
walletIntent
|
||||
} else {
|
||||
try {
|
||||
context.packageManager.getLaunchIntentForPackage(WALLET_PACKAGE_NAME)
|
||||
?.apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
|
||||
.also { walletIntent = it }
|
||||
} catch (exception: Exception) {
|
||||
Timber.tag(TAG).e(exception)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M24,0L24,0A24,24 0,0 1,48 24L48,24A24,24 0,0 1,24 48L24,48A24,24 0,0 1,0 24L0,24A24,24 0,0 1,24 0z"
|
||||
android:fillColor="#393C45"/>
|
||||
<path
|
||||
android:pathData="M34.092,21.69V18.629C34.092,16.861 32.652,15.42 30.884,15.42H17.116C15.348,15.436 13.907,16.877 13.907,18.629V21.69C13.907,22.001 14.153,22.247 14.464,22.247H33.536C33.847,22.247 34.092,22.001 34.092,21.69Z"
|
||||
android:fillColor="#34A853"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M34.092,24.375V21.33C34.092,19.562 32.652,18.121 30.884,18.121H17.116C15.348,18.121 13.907,19.562 13.907,21.33V24.391C13.907,24.702 14.153,24.948 14.464,24.948H33.536C33.847,24.931 34.092,24.686 34.092,24.375Z"
|
||||
android:fillColor="#FBBC04"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M34.092,27.077V24.015C34.092,22.247 32.652,20.807 30.884,20.807H17.116C15.348,20.807 13.907,22.247 13.907,24.015V27.077C13.907,27.388 14.153,27.633 14.464,27.633H33.536C33.847,27.617 34.092,27.371 34.092,27.077Z"
|
||||
android:fillColor="#EA4335"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M26.742,25.603L13.907,22.623V29.368C13.907,31.136 15.348,32.576 17.116,32.576H30.884C32.652,32.576 34.092,31.136 34.092,29.368V22.492L31.031,24.719C29.787,25.619 28.232,25.946 26.742,25.603Z"
|
||||
android:fillColor="#4285F4"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
Loading…
Add table
Add a link
Reference in a new issue