From e17ff1b4c28fd83907d60ea08a83892c06bf6845 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 23 Nov 2022 13:49:15 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/src/main/res/values-ru/strings.xml | 1 + .../main/res/values/strings_untranslated.xml | 2 +- .../components/appbar/AppBarWithBackButton.kt | 38 +-- .../src/main/res/values-ru/string_common.xml | 1 + core/ui/src/main/res/values-ru/strings.xml | 4 + core/ui/src/main/res/values/dimens.xml | 6 +- core/ui/src/main/res/values/string_common.xml | 1 + core/ui/src/main/res/values/strings.xml | 4 + .../feature/referral/ReferralFragment.kt | 9 +- .../referral/models/ReferralStateHolder.kt | 14 +- .../feature/referral/router/ReferralRouter.kt | 11 + .../ui/AgreementBottomSheetContent.kt | 2 +- .../feature/referral/ui/AgreementText.kt | 42 +-- .../referral/ui/NonParticipateBottomBlock.kt | 2 +- .../referral/ui/ParticipateBottomBlock.kt | 44 ++- .../feature/referral/ui/ReferralScreen.kt | 300 +++++++++++++----- .../referral/viewmodels/ReferralViewModel.kt | 75 +++-- .../src/main/res/values-ru/string.xml | 6 +- .../res/values/{string.xml => strings.xml} | 6 +- 19 files changed, 391 insertions(+), 177 deletions(-) create mode 100644 core/ui/src/main/res/values-ru/strings.xml create mode 100644 core/ui/src/main/res/values/strings.xml create mode 100644 features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt rename features/referral/presentation/src/main/res/values/{string.xml => strings.xml} (66%) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a709a41b26..57273eb0aa 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -175,6 +175,7 @@ Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. Блокчейн недоступен. Попробуй позже. Хорошо, понял! + Успешно! Начнем Выполнить резервное копирование Оставить на потом diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 59a680c908..d43c30aab9 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -153,6 +153,7 @@ Ok, Got it! + Success! Getting started Backup now @@ -222,7 +223,6 @@ - Dapp %s, requesting to\n sign BNB transaction \n diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt index 629f6f7592..3f4c5efa9e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/AppBarWithBackButton.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.components.appbar +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -18,22 +19,6 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.res.TangemTheme -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -fun PreviewAppBarWithBackButtonInLightTheme() { - TangemTheme(isDark = false) { - AppBarWithBackButton(text = "Title", onBackClick = {}) - } -} - -@Preview(widthDp = 360, heightDp = 56, showBackground = true) -@Composable -fun PreviewAppBarWithBackButtonInDarkTheme() { - TangemTheme(isDark = true) { - AppBarWithBackButton(text = "Title", onBackClick = {}) - } -} - /** * App bar with back button and optional title * @@ -47,8 +32,9 @@ fun PreviewAppBarWithBackButtonInDarkTheme() { fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { Row( modifier = Modifier - .padding(all = dimensionResource(R.dimen.spacing16)) - .fillMaxWidth(), + .background(MaterialTheme.colors.primary) + .fillMaxWidth() + .padding(all = dimensionResource(R.dimen.spacing16)), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.spacing16)), verticalAlignment = Alignment.CenterVertically, ) { @@ -69,4 +55,20 @@ fun AppBarWithBackButton(text: String? = null, onBackClick: () -> Unit) { ) } } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun PreviewAppBarWithBackButtonInLightTheme() { + TangemTheme(isDark = false) { + AppBarWithBackButton(text = "Title", onBackClick = {}) + } +} + +@Preview(widthDp = 360, heightDp = 56, showBackground = true) +@Composable +fun PreviewAppBarWithBackButtonInDarkTheme() { + TangemTheme(isDark = true) { + AppBarWithBackButton(text = "Title", onBackClick = {}) + } } \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/string_common.xml b/core/ui/src/main/res/values-ru/string_common.xml index ebb5d6b913..7df281b0b0 100644 --- a/core/ui/src/main/res/values-ru/string_common.xml +++ b/core/ui/src/main/res/values-ru/string_common.xml @@ -2,4 +2,5 @@ Поделиться Копировать + условия участия \ No newline at end of file diff --git a/core/ui/src/main/res/values-ru/strings.xml b/core/ui/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000000..61522a01ed --- /dev/null +++ b/core/ui/src/main/res/values-ru/strings.xml @@ -0,0 +1,4 @@ + + + Хорошо, понял! + \ No newline at end of file diff --git a/core/ui/src/main/res/values/dimens.xml b/core/ui/src/main/res/values/dimens.xml index 98fbe4b379..60c2e660fb 100644 --- a/core/ui/src/main/res/values/dimens.xml +++ b/core/ui/src/main/res/values/dimens.xml @@ -13,7 +13,9 @@ + 2dp 6dp + 8dp 12dp 16dp @@ -43,12 +45,14 @@ 8dp 10dp 12dp + 14dp 16dp 24dp 32dp 44dp 50dp 54dp - 2dp + 56dp + 92dp \ No newline at end of file diff --git a/core/ui/src/main/res/values/string_common.xml b/core/ui/src/main/res/values/string_common.xml index 85e2e9ef97..b7a15f7fb0 100644 --- a/core/ui/src/main/res/values/string_common.xml +++ b/core/ui/src/main/res/values/string_common.xml @@ -2,4 +2,5 @@ Share Copy + terms and conditions \ No newline at end of file diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml new file mode 100644 index 0000000000..077070e47c --- /dev/null +++ b/core/ui/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Ok, Got it! + \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt index 34df965d31..6ce31cfa5a 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt @@ -5,19 +5,22 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView +import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import com.tangem.feature.referral.router.ReferralRouter import com.tangem.feature.referral.ui.ReferralScreen import com.tangem.feature.referral.viewmodels.ReferralViewModel import dagger.hilt.android.AndroidEntryPoint +import java.lang.ref.WeakReference @AndroidEntryPoint class ReferralFragment : Fragment() { - private val viewModel by viewModels().apply { - // TODO("[REDACTED_JIRA]") this.value.setOnBackClicked() - } + private val viewModel by viewModels() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) } + viewModel.setRouter(ReferralRouter(fragmentManager = WeakReference(parentFragmentManager))) return ComposeView(inflater.context).apply { setContent { ReferralScreen(stateHolder = viewModel.uiState) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt index e001315199..11ecb1e075 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/models/ReferralStateHolder.kt @@ -1,15 +1,16 @@ package com.tangem.feature.referral.models -data class ReferralStateHolder( +internal data class ReferralStateHolder( val headerState: HeaderState, val referralInfoState: ReferralInfoState, - val errorToast: ErrorToast, + val errorSnackbar: ErrorSnackbar? = null, ) { data class HeaderState(val onBackClicked: () -> Unit) sealed interface ReferralInfoContentState { val award: String + val networkName: String val discount: String val url: String } @@ -17,15 +18,18 @@ data class ReferralStateHolder( sealed interface ReferralInfoState { data class ParticipantContent( override val award: String, + override val networkName: String, val address: String, override val discount: String, val purchasedWalletCount: Int, val code: String, + val shareLink: String, override val url: String, ) : ReferralInfoState, ReferralInfoContentState data class NonParticipantContent( override val award: String, + override val networkName: String, override val discount: String, override val url: String, val onParticipateClicked: () -> Unit, @@ -34,8 +38,8 @@ data class ReferralStateHolder( object Loading : ReferralInfoState } - data class ErrorToast( - val visibility: Boolean, - val changeVisibility: () -> Unit + data class ErrorSnackbar( + val throwable: Throwable, + val onOkClicked: () -> Unit, ) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt new file mode 100644 index 0000000000..ecd73795bf --- /dev/null +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/router/ReferralRouter.kt @@ -0,0 +1,11 @@ +package com.tangem.feature.referral.router + +import androidx.fragment.app.FragmentManager +import java.lang.ref.WeakReference + +internal class ReferralRouter(private val fragmentManager: WeakReference) { + + fun back() { + fragmentManager.get()?.popBackStack() + } +} \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt index e7d30282c8..7bd1358fd0 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementBottomSheetContent.kt @@ -29,7 +29,7 @@ import com.tangem.feature.referral.presentation.R * @param url link to the html page */ @Composable -fun AgreementBottomSheetContent(url: String) { +internal fun AgreementBottomSheetContent(url: String) { Column( modifier = Modifier .background(color = MaterialTheme.colors.secondary) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt index 1e2ceefc51..b77f8c8377 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/AgreementText.kt @@ -22,28 +22,8 @@ import com.tangem.core.ui.res.TextColorType import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R -@Preview(widthDp = 360, showBackground = true) @Composable -fun Preview_AgreementText_InLightTheme() { - TangemTheme(isDark = false) { - Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { - AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) - } - } -} - -@Preview(widthDp = 360, showBackground = true) -@Composable -fun Preview_AgreementText_InDarkTheme() { - TangemTheme(isDark = true) { - Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { - AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) - } - } -} - -@Composable -fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit) { +internal fun AgreementText(@StringRes firstPartResId: Int, onClicked: () -> Unit) { val agreementText = annotatedAgreementString(firstPart = stringResource(firstPartResId)) ClickableText( text = agreementText, @@ -74,4 +54,24 @@ private fun annotatedAgreementString(firstPart: String): AnnotatedString { append(" ${stringResource(id = R.string.referral_tos_suffix)}") } } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_AgreementText_InLightTheme() { + TangemTheme(isDark = false) { + Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { + AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) + } + } +} + +@Preview(widthDp = 360, showBackground = true) +@Composable +fun Preview_AgreementText_InDarkTheme() { + TangemTheme(isDark = true) { + Box(modifier = Modifier.background(MaterialTheme.colors.primary)) { + AgreementText(firstPartResId = R.string.referral_tos_not_enroled_prefix, onClicked = {}) + } + } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt index a98b1e8a93..1407434cb7 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/NonParticipateBottomBlock.kt @@ -14,7 +14,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.referral.presentation.R @Composable -fun NonParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) { +internal fun NonParticipateBottomBlock(onAgreementClicked: () -> Unit, onParticipateClicked: () -> Unit) { Column { AgreementText( firstPartResId = R.string.referral_tos_not_enroled_prefix, diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt index 775896aa6c..987d178626 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ParticipateBottomBlock.kt @@ -1,5 +1,6 @@ package com.tangem.feature.referral.ui +import android.content.Context import android.content.Intent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -14,8 +15,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -29,10 +32,12 @@ import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.presentation.R @Composable -fun ParticipateBottomBlock( +internal fun ParticipateBottomBlock( purchasedWalletCount: Int, code: String, + shareLink: String, onAgreementClicked: () -> Unit, + showCopySnackbar: () -> Unit, ) { Column( modifier = Modifier @@ -51,7 +56,7 @@ fun ParticipateBottomBlock( ), ) PersonalCodeCard(code = code) - AdditionalButtons(code = code) + AdditionalButtons(shareLink = shareLink, showCopySnackbar = showCopySnackbar) AgreementText(firstPartResId = R.string.referral_tos_enroled_prefix, onClicked = onAgreementClicked) } } @@ -89,9 +94,9 @@ private fun PersonalCodeCard(code: String) { } @Composable -private fun AdditionalButtons(code: String) { +private fun AdditionalButtons(shareLink: String, showCopySnackbar: () -> Unit) { val clipboardManager = LocalClipboardManager.current - val context = LocalContext.current + val hapticFeedback = LocalHapticFeedback.current Row( modifier = Modifier.fillMaxWidth(), @@ -101,25 +106,36 @@ private fun AdditionalButtons(code: String) { modifier = Modifier.weight(1f), text = stringResource(id = R.string.common_copy), iconResId = R.drawable.ic_copy, - onClicked = { clipboardManager.setText(AnnotatedString(code)) }, + onClicked = { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + clipboardManager.setText(AnnotatedString(shareLink)) + showCopySnackbar() + }, ) + + val context = LocalContext.current PrimaryStartIconButton( modifier = Modifier.weight(1f), text = stringResource(id = R.string.common_share), iconResId = R.drawable.ic_share, onClicked = { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, code) - type = "text/plain" - } - val shareIntent = Intent.createChooser(sendIntent, null) - startActivity(context, shareIntent, null) + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + context.shareText(shareLink) }, ) } } +private fun Context.shareText(text: String) { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, text) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(this, shareIntent, null) +} + @Preview(widthDp = 360, showBackground = true) @Composable fun Preview_ParticipateBottomBlock_InLightTheme() { @@ -128,7 +144,9 @@ fun Preview_ParticipateBottomBlock_InLightTheme() { ParticipateBottomBlock( purchasedWalletCount = 3, code = "x4JdK", + shareLink = "", onAgreementClicked = {}, + showCopySnackbar = {}, ) } } @@ -142,7 +160,9 @@ fun Preview_ParticipateBottomBlock_InDarkTheme() { ParticipateBottomBlock( purchasedWalletCount = 3, code = "x4JdK", + shareLink = "", onAgreementClicked = {}, + showCopySnackbar = {}, ) } } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt index e58513e0fb..aab032565f 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt @@ -1,38 +1,50 @@ package com.tangem.feature.referral.ui -import android.widget.Toast import androidx.annotation.DrawableRes +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image +import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.BottomSheetScaffold import androidx.compose.material.BottomSheetState import androidx.compose.material.BottomSheetValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.Snackbar +import androidx.compose.material.SnackbarDuration +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState +import androidx.compose.material.SnackbarResult import androidx.compose.material.Text import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.MutableState import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -41,8 +53,10 @@ 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.VerticalSpacer import com.tangem.core.ui.components.appbar.AppBarWithBackButton +import com.tangem.core.ui.res.Black import com.tangem.core.ui.res.ButtonColorType import com.tangem.core.ui.res.IconColorType import com.tangem.core.ui.res.TangemColorPalette @@ -52,6 +66,7 @@ import com.tangem.core.ui.res.buttonColor import com.tangem.core.ui.res.iconColor import com.tangem.core.ui.res.textColor import com.tangem.feature.referral.models.ReferralStateHolder +import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoContentState import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState import com.tangem.feature.referral.presentation.R @@ -65,7 +80,7 @@ import kotlinx.coroutines.launch */ @OptIn(ExperimentalMaterialApi::class) @Composable -fun ReferralScreen(stateHolder: ReferralStateHolder) { +internal fun ReferralScreen(stateHolder: ReferralStateHolder) { val coroutineScope = rememberCoroutineScope() val bottomSheetScaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed), @@ -75,7 +90,11 @@ fun ReferralScreen(stateHolder: ReferralStateHolder) { BottomSheetScaffold( sheetContent = { AgreementBottomSheetContent( - url = (stateHolder.referralInfoState as? ReferralInfoContentState)?.url ?: "", + url = when (val state = stateHolder.referralInfoState) { + is ReferralInfoState.NonParticipantContent -> state.url + is ReferralInfoState.ParticipantContent -> state.url + is ReferralInfoState.Loading -> "" + }, ) }, scaffoldState = bottomSheetScaffoldState, @@ -100,66 +119,92 @@ fun ReferralScreen(stateHolder: ReferralStateHolder) { ) }, ) + } +} - val context = LocalContext.current - SideEffect { - if (stateHolder.errorToast.visibility) { - //TODO("[REDACTED_JIRA]") - Toast.makeText(context, "", Toast.LENGTH_LONG).show() - stateHolder.errorToast.changeVisibility() +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun ReferralContent(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) { + val isCopyButtonPressed = remember { mutableStateOf(false) } + + Box { + CompositionLocalProvider(LocalOverscrollConfiguration provides null) { + LazyColumn( + modifier = Modifier + .background(color = MaterialTheme.colors.primary) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + stickyHeader { + AppBarWithBackButton( + text = stringResource(R.string.details_referral_title), + onBackClick = stateHolder.headerState.onBackClicked, + ) + } + item { Header() } + item { + ReferralInfo( + stateHolder = stateHolder, + onAgreementClicked = onAgreementClicked, + showCopySnackbar = { isCopyButtonPressed.value = true }, + ) + } } } + + ErrorSnackbarHost(errorSnackbar = stateHolder.errorSnackbar) + CopySnackbarHost(isCopyButtonPressed = isCopyButtonPressed) } } @Composable -fun ReferralContent(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) { - Column( - modifier = Modifier - .background(color = MaterialTheme.colors.primary) - .fillMaxSize() - .verticalScroll(rememberScrollState()), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Header(stateHolder = stateHolder) +private fun Header() { + Column { + Image( + painter = painterResource(R.drawable.ill_businessman_3d), + contentDescription = null, + modifier = Modifier + .padding(horizontal = dimensionResource(id = R.dimen.spacing32)) + .fillMaxWidth() + .height(dimensionResource(R.dimen.size200)), + ) + VerticalSpacer(spaceResId = R.dimen.spacing24) + Text( + text = stringResource(id = R.string.referral_title), + modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.spacing50)), + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), + textAlign = TextAlign.Center, + maxLines = 2, + style = MaterialTheme.typography.h2, + ) VerticalSpacer(spaceResId = R.dimen.spacing16) - ReferralInfo(stateHolder = stateHolder, onAgreementClicked = onAgreementClicked) } } @Composable -private fun ColumnScope.Header(stateHolder: ReferralStateHolder) { - AppBar(stateHolder = stateHolder) - Image( - painter = painterResource(R.drawable.ill_businessman_3d), - contentDescription = null, - modifier = Modifier - .padding(horizontal = dimensionResource(id = R.dimen.spacing32)) - .fillMaxWidth() - .height(dimensionResource(R.dimen.size200)), - ) - VerticalSpacer(spaceResId = R.dimen.spacing24) - Text( - text = stringResource(id = R.string.referral_title), - modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.spacing50)), - color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY1), - textAlign = TextAlign.Center, - maxLines = 2, - style = MaterialTheme.typography.h2, - ) -} - -@Composable -fun ReferralInfo(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Unit) { +private fun ReferralInfo( + stateHolder: ReferralStateHolder, + onAgreementClicked: () -> Unit, + showCopySnackbar: () -> Unit, +) { when (val state = stateHolder.referralInfoState) { is ReferralInfoState.ParticipantContent -> { Conditions(state = state) - ParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked) + ParticipateBottomBlock( + purchasedWalletCount = state.purchasedWalletCount, + code = state.code, + shareLink = state.shareLink, + onAgreementClicked = onAgreementClicked, + showCopySnackbar = showCopySnackbar, + ) } is ReferralInfoState.NonParticipantContent -> { Conditions(state = state) VerticalSpacer(spaceResId = R.dimen.spacing44) - NonParticipantBottomBlock(state = state, onAgreementClicked = onAgreementClicked) + NonParticipateBottomBlock( + onAgreementClicked = onAgreementClicked, + onParticipateClicked = state.onParticipateClicked, + ) } is ReferralInfoState.Loading -> { LoadingCondition(iconResId = R.drawable.ic_tether) @@ -169,38 +214,34 @@ fun ReferralInfo(stateHolder: ReferralStateHolder, onAgreementClicked: () -> Uni } } -@Composable -private fun AppBar(stateHolder: ReferralStateHolder) { - AppBarWithBackButton( - text = stringResource(R.string.details_referral_title), - onBackClick = stateHolder.headerState.onBackClicked, - ) -} - @Composable private fun Conditions(state: ReferralInfoContentState) { ConditionForYou(state = state) VerticalSpacer(spaceResId = R.dimen.spacing32) - ConditionForYourFriend(state = state) + ConditionForYourFriend(discount = state.discount) } @Composable private fun ConditionForYou(state: ReferralInfoContentState) { Condition(iconResId = R.drawable.ic_tether) { when (state) { - is ReferralInfoState.ParticipantContent -> InfoForYou(award = state.award, address = state.address) - is ReferralInfoState.NonParticipantContent -> InfoForYou(award = state.award) + is ReferralInfoState.ParticipantContent -> InfoForYou( + award = state.award, + networkName = state.networkName, + address = state.address, + ) + is ReferralInfoState.NonParticipantContent -> InfoForYou( + award = state.award, + networkName = state.networkName, + ) } } } @Composable -private fun ConditionForYourFriend(state: ReferralInfoContentState) { +private fun ConditionForYourFriend(discount: String) { Condition(iconResId = R.drawable.ic_discount) { - when (state) { - is ReferralInfoState.ParticipantContent -> InfoForYourFriend(discount = state.discount) - is ReferralInfoState.NonParticipantContent -> InfoForYourFriend(discount = state.discount) - } + InfoForYourFriend(discount = discount) } } @@ -239,7 +280,7 @@ private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () -> } @Composable -private fun InfoForYou(award: String, address: String? = null) { +private fun InfoForYou(award: String, networkName: String, address: String? = null) { ConditionInfo(title = stringResource(id = R.string.referral_point_currencies_title)) { Text( text = buildAnnotatedString { @@ -250,7 +291,8 @@ private fun InfoForYou(award: String, address: String? = null) { append( String.format( stringResource(id = R.string.referral_point_currencies_description_suffix), - if (!address.isNullOrBlank()) address else "", + networkName, + if (!address.isNullOrBlank()) " $address" else "", ), ) }, @@ -319,21 +361,105 @@ private fun ShimmerInfo() { } } +// TODO() Replace component with component from ds @Composable -private fun ParticipantBottomBlock(state: ReferralInfoState.ParticipantContent, onAgreementClicked: () -> Unit) { - ParticipateBottomBlock( - purchasedWalletCount = state.purchasedWalletCount, - code = state.code, - onAgreementClicked = onAgreementClicked, - ) +private fun ErrorSnackbarHost(errorSnackbar: ErrorSnackbar?) { + if (errorSnackbar != null) { + val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } + val coroutineScope = rememberCoroutineScope() + + SnackbarHost( + hostState = snackbarHostState, + modifier = Modifier.padding(top = dimensionResource(id = R.dimen.spacing56)), + snackbar = { + Snackbar( + snackbarData = it, + modifier = Modifier.fillMaxWidth(), + actionOnNewLine = true, + shape = RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8)), + backgroundColor = Black, + contentColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + actionColor = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + elevation = dimensionResource(id = R.dimen.elevation3), + ) + }, + ) + + val message = 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) + } + val actionLabel = stringResource(id = R.string.warning_button_ok) + + 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 NonParticipantBottomBlock(state: ReferralInfoState.NonParticipantContent, onAgreementClicked: () -> Unit) { - NonParticipateBottomBlock( - onAgreementClicked = onAgreementClicked, - onParticipateClicked = state.onParticipateClicked, - ) +private fun CopySnackbarHost(isCopyButtonPressed: MutableState) { + if (isCopyButtonPressed.value) { + val snackbarHostState by remember { mutableStateOf(SnackbarHostState()) } + val coroutineScope = rememberCoroutineScope() + + var snackbarSize by remember { mutableStateOf(0) } + val width = LocalConfiguration.current.screenWidthDp.dp + val snackbarWidth = with(LocalDensity.current) { snackbarSize.toDp() } + + SnackbarHost( + hostState = snackbarHostState, + modifier = Modifier + .padding(top = dimensionResource(id = R.dimen.spacing56), start = (width - snackbarWidth).div(2)) + .fillMaxWidth(), + snackbar = { + Box( + modifier = Modifier + .onSizeChanged { snackbarSize = it.width } + .background(Black, RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8))) + .shadow( + dimensionResource(id = R.dimen.elevation3), + RoundedCornerShape(size = dimensionResource(id = R.dimen.radius8)), + ) + .padding( + horizontal = dimensionResource(id = R.dimen.spacing16), + vertical = dimensionResource(id = R.dimen.spacing14), + ), + ) { + Text( + text = it.message, + color = MaterialTheme.colors.textColor(type = TextColorType.PRIMARY2), + style = MaterialTheme.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 + } + } + } } @Preview(widthDp = 360, showBackground = true) @@ -345,13 +471,15 @@ fun Preview_ReferralScreen_Participant_InLightTheme() { headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.ParticipantContent( award = "10 USDT", + networkName = "Tron", address = "ma80...zk8q2", discount = "10%", purchasedWalletCount = 3, code = "x4JdK", + shareLink = "", url = "", ), - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -366,13 +494,15 @@ fun Preview_ReferralScreen_Participant_InDarkTheme() { headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.ParticipantContent( award = "10 USDT", + networkName = "Tron", address = "ma80...zk8q2", discount = "10%", purchasedWalletCount = 3, code = "x4JdK", + shareLink = "", url = "", ), - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -387,11 +517,12 @@ fun Preview_ReferralScreen_NonParticipant_InLightTheme() { headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.NonParticipantContent( award = "10 USDT", + networkName = "Tron", discount = "10%", url = "", onParticipateClicked = {}, ), - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -406,11 +537,12 @@ fun Preview_ReferralScreen_NonParticipant_InDarkTheme() { headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.NonParticipantContent( award = "10 USDT", + networkName = "Tron", discount = "10%", url = "", onParticipateClicked = {}, ), - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -424,7 +556,7 @@ fun Preview_ReferralScreen_Loading_InLightTheme() { stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.Loading, - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } @@ -438,7 +570,7 @@ fun Preview_ReferralScreen_Loading_InDarkTheme() { stateHolder = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = {}), referralInfoState = ReferralInfoState.Loading, - errorToast = ReferralStateHolder.ErrorToast(visibility = false, changeVisibility = {}), + errorSnackbar = null, ), ) } diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt index 4c62d3ee5a..e0a8546e4b 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/viewmodels/ReferralViewModel.kt @@ -8,16 +8,20 @@ import androidx.lifecycle.viewModelScope import com.tangem.feature.referral.domain.ReferralInteractor import com.tangem.feature.referral.domain.models.DiscountType import com.tangem.feature.referral.domain.models.ReferralData +import com.tangem.feature.referral.domain.models.ReferralInfo import com.tangem.feature.referral.models.ReferralStateHolder +import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState +import com.tangem.feature.referral.router.ReferralRouter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.runCatching import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject +import kotlin.properties.Delegates @HiltViewModel -class ReferralViewModel @Inject constructor( +internal class ReferralViewModel @Inject constructor( private val referralInteractor: ReferralInteractor, private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel() { @@ -25,57 +29,80 @@ class ReferralViewModel @Inject constructor( var uiState: ReferralStateHolder by mutableStateOf(createInitiallyUiState()) private set + private var referralRouter: ReferralRouter by Delegates.notNull() + init { loadReferralData() } - fun setOnBackClicked(onBackClicked: () -> Unit) { - uiState = uiState.copy(headerState = ReferralStateHolder.HeaderState(onBackClicked = onBackClicked)) + fun setRouter(router: ReferralRouter) { + referralRouter = router + uiState = uiState.copy(headerState = ReferralStateHolder.HeaderState(onBackClicked = router::back)) } private fun createInitiallyUiState() = ReferralStateHolder( headerState = ReferralStateHolder.HeaderState(onBackClicked = { }), referralInfoState = ReferralInfoState.Loading, - errorToast = ReferralStateHolder.ErrorToast( - visibility = false, - changeVisibility = { - uiState = uiState.copy( - referralInfoState = uiState.referralInfoState, - errorToast = uiState.errorToast.copy(visibility = false), - ) - }, - ), + errorSnackbar = null, ) private fun loadReferralData() { - val lastUiState = uiState uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) viewModelScope.launch(dispatchers.main) { runCatching(dispatchers.io) { referralInteractor.getReferralStatus() } - .onSuccess { uiState = uiState.copy(referralInfoState = it.convertToReferralInfoState()) } - .onFailure { uiState = lastUiState.copy(errorToast = lastUiState.errorToast.copy(visibility = true)) } + .onSuccess(::showContent) + .onFailure(::showErrorSnackbar) } } + private fun participate() { + uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) + viewModelScope.launch(dispatchers.main) { + runCatching(dispatchers.io) { referralInteractor.startReferral() } + .onSuccess(::showContent) + .onFailure(::showErrorSnackbar) + } + } + + private fun showContent(referralData: ReferralData) { + uiState = uiState.copy(referralInfoState = referralData.convertToReferralInfoState()) + } + + private fun showErrorSnackbar(throwable: Throwable) { + uiState = uiState.copy( + errorSnackbar = ErrorSnackbar(throwable = throwable, onOkClicked = referralRouter::back), + ) + } + private fun ReferralData.convertToReferralInfoState(): ReferralInfoState = when (this) { is ReferralData.ParticipantData -> ReferralInfoState.ParticipantContent( award = getAwardValue(), - address = referral.address, + networkName = getNetworkName(), + address = referral.getAddressValue(), discount = getDiscountValue(), purchasedWalletCount = referral.walletsPurchased, code = referral.promocode, + shareLink = referral.shareLink, url = tosLink, ) is ReferralData.NonParticipantData -> ReferralInfoState.NonParticipantContent( award = getAwardValue(), + networkName = getNetworkName(), discount = getDiscountValue(), url = tosLink, - onParticipateClicked = ::onParticipateClicked, + onParticipateClicked = ::participate, ) } - private fun ReferralData.getAwardValue(): String = - "$award ${requireNotNull(tokens.firstOrNull()?.symbol) { "Token list is empty" }}" + private fun ReferralData.getAwardValue(): String = "$award ${getToken().symbol}" + + private fun ReferralData.getNetworkName(): String = getToken().networkId.replaceFirstChar(Char::uppercase) + + private fun ReferralInfo.getAddressValue(): String { + check(address.length > 5) { "Invalid address" } + return address.substring(startIndex = 0, endIndex = 4) + "..." + + address.substring(startIndex = address.length - 5, endIndex = address.length) + } private fun ReferralData.getDiscountValue(): String { val discountSymbol = when (discountType) { @@ -85,13 +112,5 @@ class ReferralViewModel @Inject constructor( return "$discount $discountSymbol" } - private fun onParticipateClicked() { - val lastUiState = uiState - uiState = uiState.copy(referralInfoState = ReferralInfoState.Loading) - viewModelScope.launch(dispatchers.main) { - runCatching(dispatchers.io) { referralInteractor.startReferral() } - .onSuccess { uiState = uiState.copy(referralInfoState = it.convertToReferralInfoState()) } - .onFailure { uiState = lastUiState.copy(errorToast = lastUiState.errorToast.copy(visibility = true)) } - } - } + private fun ReferralData.getToken() = requireNotNull(tokens.firstOrNull()) { "Token list is empty" } } \ No newline at end of file diff --git a/features/referral/presentation/src/main/res/values-ru/string.xml b/features/referral/presentation/src/main/res/values-ru/string.xml index 5e6ef22607..16daf7f17e 100644 --- a/features/referral/presentation/src/main/res/values-ru/string.xml +++ b/features/referral/presentation/src/main/res/values-ru/string.xml @@ -4,7 +4,7 @@ Приведи друга в Tangem Вы Получите - за каждый кошелек, который купит ваш друг на ваш адрес в сети Tron %s + на ваш адрес в сети %s%s за каждый кошелек, который купит ваш друг Ваш друг Получит %s скидку @@ -17,4 +17,8 @@ Ваш персональный код %d кошельков Ваши друзья купили + Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. + Не удалось загрузить информацию по реферальной программе. Причина: %s. Пожалуйста, попробуйте позже. + Не удалось обработать вашу заявку на участие. Причина: %s. Пожалуйста, попробуйте позже. Если проблема сохранится, вы можете обратиться в техподдержку. + Персональный код скопирован! \ No newline at end of file diff --git a/features/referral/presentation/src/main/res/values/string.xml b/features/referral/presentation/src/main/res/values/strings.xml similarity index 66% rename from features/referral/presentation/src/main/res/values/string.xml rename to features/referral/presentation/src/main/res/values/strings.xml index e429fe37e1..8e6c0e6912 100644 --- a/features/referral/presentation/src/main/res/values/string.xml +++ b/features/referral/presentation/src/main/res/values/strings.xml @@ -4,7 +4,7 @@ Refer your friends to Tangem You Will get - for each wallet bought by your friend on your Tron network address %s + for each wallet bought by your friend on your %s network address%s Your friend Will get a %s discount @@ -17,4 +17,8 @@ Your personal code %d wallets Your friends bought + Failed to load the information about the referral program. Please try again later. + Failed to load the information about the referral program. Reason: %s. Please try again later. + Your participation request could not be processed. Reason: %s. Please try again later. If the problem persists — feel free to contact our support. + Personal code copied! \ No newline at end of file