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