Updated on 2026-08-14

This commit is contained in:
Tangem 2024-05-17 17:11:13 +08:00
parent 70c989be4e
commit 9da26433fb
3 changed files with 74 additions and 140 deletions

View file

@ -131,6 +131,7 @@ data class TangemDimens internal constructor(
val spacing92: Dp = 92.dp,
val spacing94: Dp = 94.dp,
val spacing96: Dp = 96.dp,
val spacing108: Dp = 108.dp,
val spacing154: Dp = 154.dp,
// endregion Spacing
)

View file

@ -8,14 +8,8 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
@ -31,12 +25,13 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.core.content.ContextCompat.startActivity
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.components.PrimaryStartIconButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.referral.domain.models.ExpectedAward
import com.tangem.feature.referral.domain.models.ExpectedAwards
import com.tangem.feature.referral.presentation.R
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
@Composable
@ -45,8 +40,8 @@ internal fun ParticipateBottomBlock(
code: String,
shareLink: String,
expectedAwards: ExpectedAwards?,
snackbarHostState: SnackbarHostState,
onAgreementClick: () -> Unit,
onShowCopySnackbar: () -> Unit,
onCopyClick: () -> Unit,
onShareClick: () -> Unit,
) {
@ -63,7 +58,7 @@ internal fun ParticipateBottomBlock(
AdditionalButtons(
code = code,
shareLink = shareLink,
onShowCopySnackbar = onShowCopySnackbar,
snackbarHostState = snackbarHostState,
onCopyClick = onCopyClick,
onShareClick = onShareClick,
)
@ -112,9 +107,9 @@ private fun Awards(expectedAwards: ExpectedAwards) {
val elementsCountToShowInLessMode = 3
val isExpanded = remember { mutableStateOf(false) }
Divider(
color = TangemTheme.colors.stroke.primary,
HorizontalDivider(
thickness = TangemTheme.dimens.size0_5,
color = TangemTheme.colors.stroke.primary,
)
AwardText(
startText = if (expectedAwards.expectedAwards.isNotEmpty()) {
@ -272,13 +267,16 @@ private fun PersonalCodeCard(code: String) {
private fun AdditionalButtons(
code: String,
shareLink: String,
onShowCopySnackbar: () -> Unit,
snackbarHostState: SnackbarHostState,
onCopyClick: () -> Unit,
onShareClick: () -> Unit,
) {
val clipboardManager = LocalClipboardManager.current
val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope()
val resources = LocalContext.current.resources
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
@ -291,7 +289,13 @@ private fun AdditionalButtons(
onCopyClick.invoke()
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
clipboardManager.setText(AnnotatedString(code))
onShowCopySnackbar()
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = resources.getString(R.string.referral_promo_code_copied),
duration = SnackbarDuration.Short,
)
}
},
)
@ -333,7 +337,7 @@ private fun ParticipateBottomBlockPreview(
shareLink = data.shareLink,
expectedAwards = data.expectedAwards,
onAgreementClick = data.onAgreementClick,
onShowCopySnackbar = data.onShowCopySnackbar,
snackbarHostState = SnackbarHostState(),
onCopyClick = data.onCopyClick,
onShareClick = data.onShareClick,
)
@ -391,7 +395,6 @@ private class ParticipateBottomBlockDataProvider : CollectionPreviewParameterPro
purchasedWalletCount = 0,
expectedAwards = null,
),
),
)

View file

@ -1,6 +1,7 @@
package com.tangem.feature.referral.ui
import android.content.res.Configuration
import android.content.res.Resources
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@ -11,9 +12,7 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
@ -22,14 +21,15 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH24
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.referral.domain.models.ExpectedAward
import com.tangem.feature.referral.domain.models.ExpectedAwards
import com.tangem.feature.referral.models.DemoModeException
@ -50,6 +50,8 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
var isBottomSheetVisible by remember { mutableStateOf(value = false) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val snackbarHostState = remember(::SnackbarHostState)
Scaffold(
modifier = modifier,
topBar = {
@ -58,10 +60,26 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
onBackClick = stateHolder.headerState.onBackClicked,
)
},
snackbarHost = {
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(bottom = TangemTheme.dimens.spacing108),
) {
// TODO: use StateEvent
if (stateHolder.errorSnackbar != null) {
TangemSnackbar(data = it, actionOnNewLine = true)
} else {
CopiedTextSnackbar(it)
}
}
},
containerColor = TangemTheme.colors.background.secondary,
) {
ReferralContent(
stateHolder = stateHolder,
snackbarHostState = snackbarHostState,
onAgreementClick = {
stateHolder.analytics.onAgreementClicked.invoke()
isBottomSheetVisible = true
@ -70,6 +88,26 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
)
}
val errorSnackbar = stateHolder.errorSnackbar
val coroutineScope = rememberCoroutineScope()
val resources = LocalContext.current.resources
SideEffect {
if (errorSnackbar != null) {
coroutineScope.launch {
val result = snackbarHostState.showSnackbar(
message = resources.getMessageForErrorSnackbar(errorSnackbar.throwable),
actionLabel = resources.getString(R.string.warning_button_ok),
duration = SnackbarDuration.Indefinite,
)
if (result == SnackbarResult.ActionPerformed) {
errorSnackbar.onOkClicked()
}
}
}
}
ReferralBottomSheet(
sheetState = sheetState,
isVisible = isBottomSheetVisible,
@ -81,11 +119,10 @@ internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier
@Composable
private fun ReferralContent(
stateHolder: ReferralStateHolder,
snackbarHostState: SnackbarHostState,
onAgreementClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val isCopyButtonPressed = remember { mutableStateOf(value = false) }
Box(modifier = modifier) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
@ -95,14 +132,11 @@ private fun ReferralContent(
item {
ReferralInfo(
stateHolder = stateHolder,
snackbarHostState = snackbarHostState,
onAgreementClick = onAgreementClick,
onShowCopySnackbar = { isCopyButtonPressed.value = true },
)
}
}
ErrorSnackbarHost(errorSnackbar = stateHolder.errorSnackbar)
CopySnackbarHost(isCopyButtonPressed = isCopyButtonPressed)
}
}
@ -133,8 +167,8 @@ private fun Header() {
@Composable
private fun ReferralInfo(
stateHolder: ReferralStateHolder,
snackbarHostState: SnackbarHostState,
onAgreementClick: () -> Unit,
onShowCopySnackbar: () -> Unit,
) {
when (val state = stateHolder.referralInfoState) {
is ReferralInfoState.ParticipantContent -> {
@ -144,8 +178,8 @@ private fun ReferralInfo(
code = state.code,
shareLink = state.shareLink,
expectedAwards = state.expectedAwards,
snackbarHostState = snackbarHostState,
onAgreementClick = onAgreementClick,
onShowCopySnackbar = onShowCopySnackbar,
onCopyClick = stateHolder.analytics.onCopyClicked,
onShareClick = stateHolder.analytics.onShareClicked,
)
@ -335,115 +369,11 @@ private fun ShimmerInfo() {
}
}
// TODO() Replace component with component from ds
@Composable
private fun BoxScope.ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) {
if (errorSnackbar != null) {
val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) }
val coroutineScope = rememberCoroutineScope()
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier.align(Alignment.BottomCenter),
snackbar = {
Snackbar(
snackbarData = it,
modifier = Modifier.fillMaxWidth(),
actionOnNewLine = true,
shape = RoundedCornerShape(size = TangemTheme.dimens.radius8),
containerColor = TangemTheme.colors.button.primary,
contentColor = TangemTheme.colors.text.primary2,
actionColor = TangemTheme.colors.text.primary2,
)
},
)
val actionLabel = stringResource(id = R.string.warning_button_ok)
val message = getMessageForErrorSnackbar(errorSnackbar)
SideEffect {
coroutineScope.launch {
val result = snackbarHostState.showSnackbar(
message = message,
actionLabel = actionLabel,
duration = SnackbarDuration.Indefinite,
)
if (result == SnackbarResult.ActionPerformed) {
errorSnackbar.onOkClicked()
}
}
}
}
}
// TODO() Replace component with component from ds
@Composable
private fun BoxScope.CopySnackbarHost(isCopyButtonPressed: MutableState<Boolean>) {
if (isCopyButtonPressed.value) {
val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) }
val coroutineScope = rememberCoroutineScope()
var snackbarSize by remember { mutableIntStateOf(value = 0) }
val width = LocalConfiguration.current.screenWidthDp.dp
val snackbarWidth = with(LocalDensity.current) { snackbarSize.toDp() }
SnackbarHost(
hostState = snackbarHostState,
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(start = (width - snackbarWidth).div(2), bottom = TangemTheme.dimens.spacing12)
.fillMaxWidth(),
snackbar = {
Box(
modifier = Modifier
.onSizeChanged { snackbarSize = it.width }
.background(
color = TangemTheme.colors.icon.primary1,
shape = RoundedCornerShape(size = TangemTheme.dimens.radius8),
)
.padding(
horizontal = TangemTheme.dimens.spacing16,
vertical = TangemTheme.dimens.spacing14,
),
) {
Text(
text = it.visuals.message,
color = TangemTheme.colors.text.primary2,
style = TangemTheme.typography.body2,
)
}
},
)
val message = stringResource(id = R.string.referral_promo_code_copied)
SideEffect {
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = message,
duration = SnackbarDuration.Short,
)
isCopyButtonPressed.value = false
}
}
}
}
@Composable
private fun getMessageForErrorSnackbar(errorSnackbar: ErrorSnackbar): String {
return when (errorSnackbar.throwable) {
is DemoModeException -> {
stringResource(id = R.string.alert_demo_feature_disabled)
}
else -> {
if (errorSnackbar.throwable.cause != null) {
String.format(
format = stringResource(id = R.string.referral_error_failed_to_load_info_with_reason),
errorSnackbar.throwable.cause,
)
} else {
stringResource(id = R.string.referral_error_failed_to_load_info)
}
}
private fun Resources.getMessageForErrorSnackbar(throwable: Throwable): String {
return when {
throwable is DemoModeException -> getString(R.string.alert_demo_feature_disabled)
throwable.cause != null -> getString(R.string.referral_error_failed_to_load_info_with_reason, throwable.cause)
else -> getString(R.string.referral_error_failed_to_load_info)
}
}