Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-04 13:40:23 +05:00
parent 7dab5f79b2
commit cc3a64bb05
15 changed files with 144 additions and 21 deletions

View file

@ -52,13 +52,13 @@ fun NavigationButtonsBlock(
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
PreviousButton(state?.prevButton)
PrimaryButton(state?.primaryButton, modifier = Modifier.weight(1f))
NavigationPrimaryButton(state?.primaryButton, modifier = Modifier.weight(1f))
}
}
}
@Composable
private fun PrimaryButton(primaryButton: NavigationButton?, modifier: Modifier = Modifier) {
fun NavigationPrimaryButton(primaryButton: NavigationButton?, modifier: Modifier = Modifier) {
val wrappedButton by rememberNavigationButton(primaryButton)
AnimatedContent(
targetState = wrappedButton,

View file

@ -12,6 +12,7 @@ sealed class NavigationButtonsState {
val prevButton: NavigationButton?,
val extraButtons: ImmutableList<NavigationButton>,
val txUrl: String? = null,
val onTextClick: (String) -> Unit,
) : NavigationButtonsState()
}

View file

@ -54,5 +54,6 @@ internal object NavigationButtonsPreview {
prevButton = prev,
extraButtons = extraButtons,
txUrl = "https://tangem.com",
onTextClick = {},
)
}

View file

@ -132,6 +132,7 @@
<string name="common_origin_card">Primär Karte</string>
<string name="common_passphrase">Passphrase</string>
<string name="common_paste">Einfügen</string>
<string name="common_privacy_policy">Datenschutzbestimmungen</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_read_more">Weiterlesen</string>
@ -159,6 +160,7 @@
<string name="common_support">Unterstützung</string>
<string name="common_swap">Tauschen</string>
<string name="common_terms_and_conditions">Allgemeine Geschäftsbedingungen</string>
<string name="common_terms_of_use">Nutzungsbedingungen</string>
<string name="common_today">Heute</string>
<string name="common_transaction_failed">Transaktion fehlgeschlagen</string>
<string name="common_transactions">Transaktionen</string>
@ -264,7 +266,6 @@
<string name="express_legal_one_placeholder">Durch die Nutzung der Swap-Funktion erklärst du dich mit den folgenden Bedingungen des Anbieters einverstanden %s</string>
<string name="express_legal_two_placeholders">Durch die Nutzung der Swap-Funktionalität erklärst du dich mit des Anbieters %1$s und %2$s einverstanden</string>
<string name="express_more_providers_soon">Weitere Anbieter werden bald folgen.\nBleib dabei!</string>
<string name="express_privacy_policy">Datenschutzbestimmungen</string>
<string name="express_provider">Anbieter</string>
<string name="express_provider_best_rate">Bester Preis</string>
<string name="express_provider_max_amount">Verfügbar bis zu %s</string>
@ -272,7 +273,6 @@
<string name="express_provider_not_available">Für dieses Paar nicht verfügbar</string>
<string name="express_provider_permission_needed">Erlaubnis erforderlich</string>
<string name="express_provider_recommended">Empfohlen</string>
<string name="express_terms_of_use">Nutzungsbedingungen</string>
<string name="express_token_list_empty_search">Keine Token gefunden. Bitte versuche eine andere Anfrage</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">Transaktions-ID kopiert</string>

View file

@ -132,6 +132,7 @@
<string name="common_origin_card">Carte principale</string>
<string name="common_passphrase">Passphrase</string>
<string name="common_paste">Coller</string>
<string name="common_privacy_policy">Politique de confidentialité</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_read_more">En savoir plus</string>
@ -159,6 +160,7 @@
<string name="common_support">Support</string>
<string name="common_swap">Échanger</string>
<string name="common_terms_and_conditions">termes et conditions</string>
<string name="common_terms_of_use">Conditions d\'utilisation</string>
<string name="common_today">Aujourd\'hui</string>
<string name="common_transaction_failed">La transaction a échoué</string>
<string name="common_transactions">Transactions</string>
@ -264,7 +266,6 @@
<string name="express_legal_one_placeholder">En utilisant la fonctionnalité d\'échange, vous acceptez les %s</string>
<string name="express_legal_two_placeholders">En utilisant la fonctionnalité d\'échange, vous acceptez les conditions %1$s et %2$s du fournisseur</string>
<string name="express_more_providers_soon">D\'autres fournisseurs arriveront bientôt.\nRestez branchés !</string>
<string name="express_privacy_policy">Politique de confidentialité</string>
<string name="express_provider">Fournisseur</string>
<string name="express_provider_best_rate">Meilleur taux</string>
<string name="express_provider_max_amount">Disponible jusqu\'à %s</string>
@ -272,7 +273,6 @@
<string name="express_provider_not_available">Indisponible pour cette paire</string>
<string name="express_provider_permission_needed">Permission requise</string>
<string name="express_provider_recommended">Recommandé</string>
<string name="express_terms_of_use">Conditions d\'utilisation</string>
<string name="express_token_list_empty_search">Aucun jeton trouvé. Veuillez essayer une autre demande</string>
<string name="express_transaction_id">ID : %s</string>
<string name="express_transaction_id_copied">ID de transaction copié</string>

View file

@ -129,6 +129,7 @@
<string name="common_origin_card">プライマリーカード</string>
<string name="common_passphrase">パスフレーズ</string>
<string name="common_paste">ペースト</string>
<string name="common_privacy_policy">プライバシーポリシー</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_read_more">続きを読む</string>
@ -156,6 +157,7 @@
<string name="common_support">サポート</string>
<string name="common_swap">スワップ</string>
<string name="common_terms_and_conditions">利用規約</string>
<string name="common_terms_of_use">利用規約</string>
<string name="common_today">今日</string>
<string name="common_transaction_failed">取引が失敗しました</string>
<string name="common_transactions">取引</string>
@ -261,7 +263,6 @@
<string name="express_legal_one_placeholder">スワップ機能を使用すると、プロバイダーの%sに同意したことになります。</string>
<string name="express_legal_two_placeholders">スワップ機能を使用すると、プロバイダーの%1$sおよび%2$sに同意したことになります。</string>
<string name="express_more_providers_soon">さらに多くのプロバイダーを追加予定です。 \nお楽しみに。</string>
<string name="express_privacy_policy">プライバシーポリシー</string>
<string name="express_provider">プロバイダー</string>
<string name="express_provider_best_rate">ベストレート</string>
<string name="express_provider_max_amount">最大 %s まで使用可能</string>
@ -269,7 +270,6 @@
<string name="express_provider_not_available">このペアは利用できません</string>
<string name="express_provider_permission_needed">許可が必要です</string>
<string name="express_provider_recommended">推奨</string>
<string name="express_terms_of_use">利用規約</string>
<string name="express_token_list_empty_search">トークンが見つかりません。別のリクエストをお試しください。</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">取引IDをコピーしました</string>

View file

@ -136,6 +136,7 @@
<string name="common_origin_card">Основная карта</string>
<string name="common_passphrase">Парольная фраза</string>
<string name="common_paste">Вставить</string>
<string name="common_privacy_policy">Политикой конфиденциальности</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_read_more">Подробнее</string>
@ -163,6 +164,7 @@
<string name="common_support">Поддержка</string>
<string name="common_swap">Обмен</string>
<string name="common_terms_and_conditions">условия участия</string>
<string name="common_terms_of_use">Условиями использования</string>
<string name="common_today">Сегодня</string>
<string name="common_transaction_failed">Ошибка транзакции</string>
<string name="common_transactions">Транзакции</string>
@ -266,7 +268,6 @@
<string name="express_legal_one_placeholder">Пользуясь сервисом, вы соглашаетесь с %s</string>
<string name="express_legal_two_placeholders">Пользуясь сервисом, вы соглашаетесь с %1$s и %2$s</string>
<string name="express_more_providers_soon">Больше провайдеров на подходе.\nСледите за обновлениями!</string>
<string name="express_privacy_policy">Политикой конфиденциальности</string>
<string name="express_provider">Провайдер</string>
<string name="express_provider_best_rate">Лучший курс</string>
<string name="express_provider_max_amount">Доступно до %s</string>
@ -274,7 +275,6 @@
<string name="express_provider_not_available">Недоступно для этой пары</string>
<string name="express_provider_permission_needed">Требуется разрешение</string>
<string name="express_provider_recommended">Рекомендовано</string>
<string name="express_terms_of_use">Условиями использования</string>
<string name="express_token_list_empty_search">Токены не найдены. Пожалуйста, попробуйте другой запрос</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">ID транзакции скопирован</string>

View file

@ -129,6 +129,7 @@
<string name="common_origin_card">Основна картка</string>
<string name="common_passphrase">Парольна фраза</string>
<string name="common_paste">Вставити</string>
<string name="common_privacy_policy">Політикою конфіденційності</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_read_more">Детальніше</string>
@ -156,6 +157,7 @@
<string name="common_support">Підтримка</string>
<string name="common_swap">Обмін</string>
<string name="common_terms_and_conditions">умови участі</string>
<string name="common_terms_of_use">Умовами використання</string>
<string name="common_today">Сьогодні</string>
<string name="common_transaction_failed">Помилка транзакції</string>
<string name="common_transactions">Транзакції</string>
@ -259,7 +261,6 @@
<string name="express_legal_one_placeholder">Використовуючи сервіс обміну, ви погоджуєтеся з його %s</string>
<string name="express_legal_two_placeholders">Використовуючи сервіс обміну, ви погоджуєтеся з його %1$s та %2$s</string>
<string name="express_more_providers_soon">Незабаром з\'являться нові провайдери.\nСлідкуйте за новинами!</string>
<string name="express_privacy_policy">Політикою конфіденційності</string>
<string name="express_provider">Провайдер</string>
<string name="express_provider_best_rate">Найкращий курс</string>
<string name="express_provider_max_amount">Доступно до %s</string>
@ -267,7 +268,6 @@
<string name="express_provider_not_available">Недоступно для цієї пари</string>
<string name="express_provider_permission_needed">Потрібен дозвіл</string>
<string name="express_provider_recommended">Рекомендовано</string>
<string name="express_terms_of_use">Умовами використання</string>
<string name="express_token_list_empty_search">Токенів не знайдено. Будь ласка, спробуйте інший запит</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">ID транзакції скопійовано</string>

View file

@ -133,6 +133,7 @@
<string name="common_origin_card">Primary Card</string>
<string name="common_passphrase">Passphrase</string>
<string name="common_paste">Paste</string>
<string name="common_privacy_policy">Privacy Policy</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_range_with_space">%1$s — %2$s</string>
<string name="common_read_more">Read more</string>
@ -160,6 +161,7 @@
<string name="common_support">Support</string>
<string name="common_swap">Swap</string>
<string name="common_terms_and_conditions">terms and conditions</string>
<string name="common_terms_of_use">Terms of Use</string>
<string name="common_today">Today</string>
<string name="common_transaction_failed">Transaction failed</string>
<string name="common_transactions">Transactions</string>
@ -265,7 +267,6 @@
<string name="express_legal_one_placeholder">By using swap functionality, you agree with providers %s</string>
<string name="express_legal_two_placeholders">By using swap functionality, you agree with providers %1$s and %2$s</string>
<string name="express_more_providers_soon">More providers are coming soon.\nStay tuned!</string>
<string name="express_privacy_policy">Privacy Policy</string>
<string name="express_provider">Provider</string>
<string name="express_provider_best_rate">Best rate</string>
<string name="express_provider_max_amount">Available up to %s</string>
@ -273,7 +274,6 @@
<string name="express_provider_not_available">Unavailable for this pair</string>
<string name="express_provider_permission_needed">Permission Required</string>
<string name="express_provider_recommended">Recommended</string>
<string name="express_terms_of_use">Terms of Use</string>
<string name="express_token_list_empty_search">No tokens found. Please try another request</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">Transaction ID copied</string>
@ -664,6 +664,7 @@
<string name="staking_details_unbonding_period_info">The period you must wait after requesting to withdraw funds from staking before the tokens become available.</string>
<string name="staking_details_warmup_period">Warmup period</string>
<string name="staking_details_warmup_period_info">The allocated time for activating participation in staking.</string>
<string name="staking_legal">By using staking functionality, you agree with providers %1$s and %2$s</string>
<string name="staking_locked">Locked</string>
<string name="staking_migrate">Migrate</string>
<string name="staking_native">Native staking</string>

View file

@ -2,6 +2,7 @@ package com.tangem.features.staking.impl.presentation.state
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.TextReference
@ -19,7 +20,9 @@ import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class StakingStateController @Inject constructor() {
internal class StakingStateController @Inject constructor(
private val urlOpener: UrlOpener,
) {
val value: StakingUiState get() = uiState.value
@ -27,7 +30,7 @@ internal class StakingStateController @Inject constructor() {
val uiState: StateFlow<StakingUiState> get() = mutableUiState.asStateFlow()
private val buttonsTransformer = SetButtonsStateTransformer()
private val buttonsTransformer = SetButtonsStateTransformer(urlOpener)
private val titleTransformer = SetTitleTransformer
fun update(function: (StakingUiState) -> StakingUiState) {

View file

@ -2,6 +2,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.common.ui.navigationButtons.NavigationButton
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
@ -12,7 +13,9 @@ import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
internal class SetButtonsStateTransformer(
private val urlOpener: UrlOpener,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val confirmState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
@ -23,6 +26,7 @@ internal class SetButtonsStateTransformer : Transformer<StakingUiState> {
prevButton = getPrevButton(prevState),
extraButtons = getExtraButtons(prevState),
txUrl = (confirmState?.transactionDoneState as? TransactionDoneState.Content)?.txUrl,
onTextClick = urlOpener::openUrl,
)
} else {
NavigationButtonsState.Empty

View file

@ -28,6 +28,9 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Density
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.containers.FooterContainer
import com.tangem.core.ui.components.inputrow.InputRowDefault
@ -56,14 +59,21 @@ import kotlinx.collections.immutable.ImmutableList
private const val BANNER_BLOCK_KEY = "BannerBlock"
private const val STAKING_REWARD_BLOCK_KEY = "StakingRewardBlock"
private const val ACTIVE_STAKING_BLOCK_KEY = "ActiveStakingBlock"
private const val STAKE_PRIMARY_BUTTON_KEY = "StakePrimaryButton"
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun StakingInitialInfoContent(state: StakingStates.InitialInfoState, clickIntents: StakingClickIntents) {
internal fun StakingInitialInfoContent(
state: StakingStates.InitialInfoState,
buttonState: NavigationButtonsState,
clickIntents: StakingClickIntents,
) {
if (state !is StakingStates.InitialInfoState.Data) return
LazyColumn(
verticalArrangement = alignLastToBottom(),
modifier = Modifier
.fillMaxSize()
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
@ -105,6 +115,10 @@ internal fun StakingInitialInfoContent(state: StakingStates.InitialInfoState, cl
}
}
}
item(STAKE_PRIMARY_BUTTON_KEY) {
StakeButtonBlock(buttonState)
}
}
}
@ -236,6 +250,21 @@ private fun ActiveStakingBlock(groups: ImmutableList<BalanceGroupedState>, onCli
}
}
@Composable
private fun StakeButtonBlock(buttonState: NavigationButtonsState) {
val state = buttonState as? NavigationButtonsState.Data
val primaryButton = state?.primaryButton
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
state?.onTextClick?.let { StakingTosText(it) }
NavigationPrimaryButton(primaryButton = primaryButton)
}
}
@Composable
private fun getCaption(balanceType: BalanceType, balance: BalanceState): TextReference {
return when (balanceType) {
@ -289,6 +318,24 @@ private val textGradientColors = listOf(
Color(0xff8fb4df),
)
@Composable
private fun alignLastToBottom() = remember {
object : Arrangement.Vertical {
override fun Density.arrange(totalSize: Int, sizes: IntArray, outPositions: IntArray) {
var currentOffset = 0
sizes.forEachIndexed { index, size ->
if (index == sizes.lastIndex) {
outPositions[index] = totalSize - size
} else {
outPositions[index] = currentOffset
currentOffset += size
}
}
}
}
}
// region preview
@Preview(showBackground = true, widthDp = 360)
@ -300,6 +347,7 @@ private fun StakingInitialInfoContent_Preview(
TangemThemePreview {
StakingInitialInfoContent(
state = feeState,
buttonState = NavigationButtonsState.Empty,
clickIntents = StakingClickIntentsStub,
)
}

View file

@ -16,6 +16,7 @@ import com.tangem.common.ui.amountScreen.AmountScreenContent
import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlock
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resolveReference
@ -55,7 +56,8 @@ internal fun StakingScreen(uiState: StakingUiState) {
modifier = Modifier.weight(1f),
)
NavigationButtonsBlock(
buttonState = uiState.buttonsState,
buttonState = uiState.buttonsState.takeUnless { uiState.currentStep == StakingStep.InitialInfo }
?: NavigationButtonsState.Empty,
footerText = confirmationState?.footerText.takeIf { uiState.currentStep == StakingStep.Confirmation },
modifier = Modifier.padding(
start = TangemTheme.dimens.spacing16,
@ -153,6 +155,7 @@ private fun StakingScreenContent(uiState: StakingUiState, modifier: Modifier = M
when (state) {
StakingStep.InitialInfo -> StakingInitialInfoContent(
state = uiState.initialInfoState,
buttonState = uiState.buttonsState,
clickIntents = uiState.clickIntents,
)
StakingStep.RewardsValidators -> {

View file

@ -0,0 +1,62 @@
package com.tangem.features.staking.impl.presentation.ui
import androidx.compose.foundation.text.ClickableText
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.extensions.appendColored
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.staking.impl.R
private const val TERMS_OF_USE_KEY = "termsOfUse"
private const val PRIVACY_POLICY_KEY = "privacyPolicy"
private const val TERMS_OF_USE_URL = "https://docs.stakek.it/docs/terms-of-use"
private const val PRIVACY_POLICY_URL = "https://docs.stakek.it/docs/privacy-policy"
@Composable
internal fun StakingTosText(onTextClick: (String) -> Unit) {
val termsOfUse = stringResource(R.string.common_terms_of_use)
val privacyPolicy = stringResource(R.string.common_privacy_policy)
val tosText = stringResource(R.string.staking_legal, termsOfUse, privacyPolicy)
val clickableAnnotation = buildAnnotatedString {
append(tosText.substringBefore(termsOfUse))
pushStringAnnotation(TERMS_OF_USE_KEY, "")
appendColored(termsOfUse, TangemTheme.colors.text.accent)
pop()
append(tosText.substringAfter(termsOfUse).substringBefore(privacyPolicy))
pushStringAnnotation(PRIVACY_POLICY_KEY, "")
appendColored(privacyPolicy, TangemTheme.colors.text.accent)
pop()
}
ClickableText(
text = clickableAnnotation,
style = TangemTheme.typography.caption2.copy(
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
),
onClick = { offset ->
clickableAnnotation.getStringAnnotations(
tag = TERMS_OF_USE_KEY,
start = offset,
end = offset,
).firstOrNull()?.let {
onTextClick(TERMS_OF_USE_URL)
}
clickableAnnotation.getStringAnnotations(
tag = PRIVACY_POLICY_KEY,
start = offset,
end = offset,
).firstOrNull()?.let {
onTextClick(PRIVACY_POLICY_URL)
}
},
)
}

View file

@ -323,14 +323,14 @@ internal class StateBuilder(
return TosState(
tosLink = swapProvider.termsOfUse?.let {
LegalState(
title = resourceReference(R.string.express_terms_of_use),
title = resourceReference(R.string.common_terms_of_use),
link = it,
onClick = actions.onTosClick,
)
},
policyLink = swapProvider.privacyPolicy?.let {
LegalState(
title = resourceReference(R.string.express_privacy_policy),
title = resourceReference(R.string.common_privacy_policy),
link = it,
onClick = actions.onPolicyClick,
)