Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-07 13:04:47 +03:00
parent 93b0b05e69
commit 164b6e3c71
19 changed files with 584 additions and 65 deletions

View file

@ -0,0 +1,29 @@
package com.tangem.datasource.api.common
import com.squareup.moshi.FromJson
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import com.squareup.moshi.ToJson
import org.joda.time.LocalDate
import org.joda.time.format.DateTimeFormat
class LocalDateAdapter : JsonAdapter<LocalDate>() {
private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd")
@FromJson
override fun fromJson(reader: JsonReader): LocalDate? {
val dateString = reader.nextString()
return LocalDate.parse(dateString, formatter)
}
@ToJson
override fun toJson(writer: JsonWriter, value: LocalDate?) {
if (value != null) {
writer.value(formatter.print(value))
} else {
writer.nullValue()
}
}
}

View file

@ -1,14 +1,12 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import org.joda.time.LocalDate
/**
* Main response class for referral API
* contains all necessary info about users program status
*/
data class ReferralResponse(
@Json(name = "conditions") val conditions: Conditions,
@Json(name = "referral") val referral: Referral?,
@Json(name = "expectedAwards") val expectedAwards: ExpectedAwards?,
) {
data class Conditions(
@ -45,4 +43,16 @@ data class ReferralResponse(
@Json(name = "walletsPurchased") val walletsPurchased: Int,
@Json(name = "termsAcceptedAt") val termsAcceptedAt: String?,
)
data class ExpectedAwards(
@Json(name = "numberOfWallets") val numberOfWallets: Int,
@Json(name = "list") val list: List<AwardItem>,
) {
data class AwardItem(
@Json(name = "currency") val currency: String,
@Json(name = "paymentDate") val paymentDate: LocalDate,
@Json(name = "amount") val amount: Int,
)
}
}

View file

@ -4,6 +4,7 @@ import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.datasource.api.common.BigDecimalAdapter
import com.tangem.datasource.api.common.LocalDateAdapter
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -20,6 +21,7 @@ class MoshiModule {
fun provideNetworkMoshi(): Moshi {
return Moshi.Builder()
.add(BigDecimalAdapter())
.add(LocalDateAdapter())
.add(KotlinJsonAdapterFactory())
.build()
}

View file

@ -28,7 +28,7 @@ class NetworkModule {
fun provideTangemTechApi(@NetworkMoshi moshi: Moshi): TangemTechApi {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(PROD_TANGEM_TECH_BASE_URL)
.baseUrl(if (BuildConfig.DEBUG) DEV_TANGEM_TECH_BASE_URL else PROD_TANGEM_TECH_BASE_URL)
.client(
OkHttpClient.Builder()
.addHeaders(

View file

@ -325,8 +325,6 @@
<item quantity="other">за %d кошельков</item>
</plurals>
<string name="referral_point_currencies_description">Получите ^^%1$s^^ на ваш адрес в сети %2$s %3$s ^^спустя 30 дней^^ за каждый кошелек, который купит ваш друг</string>
<string name="referral_point_currencies_description_prefix">Получите</string>
<string name="referral_point_currencies_description_suffix">на ваш адрес в сети %1$s%2$s за каждый кошелек, который купит ваш друг</string>
<string name="referral_point_currencies_title">Вы</string>
<string name="referral_point_discount_description_prefix">Получит</string>
<string name="referral_point_discount_description_suffix">при покупке кошелька на сайте tangem.com</string>

View file

@ -259,8 +259,6 @@
<string name="referral_error_failed_to_load_info">無法加載有關推薦計劃的消息。請稍後再試</string>
<string name="referral_error_failed_to_load_info_with_reason">無法加載有關推薦計劃的消息。原因:%s。請稍後再試</string>
<string name="referral_friends_bought_title">您的朋友買</string>
<string name="referral_point_currencies_description_prefix">會得到</string>
<string name="referral_point_currencies_description_suffix">對於你的朋友在你的 %1$s 網絡地址%2$s上購買的每個錢包</string>
<string name="referral_point_currencies_title"></string>
<string name="referral_point_discount_description_prefix">得到</string>
<string name="referral_point_discount_description_value">%s 折扣</string>

View file

@ -320,8 +320,6 @@
<item quantity="other">for %d wallets</item>
</plurals>
<string name="referral_point_currencies_description">Will get ^^%1$s^^ for each wallet bought by your friend on your %2$s network address %3$s ^^30 days after^^ that</string>
<string name="referral_point_currencies_description_prefix">Will get</string>
<string name="referral_point_currencies_description_suffix">for each wallet bought by your friend on your %1$s network address%2$s</string>
<string name="referral_point_currencies_title">You</string>
<string name="referral_point_discount_description_prefix">Will get a</string>
<string name="referral_point_discount_description_suffix">when buying a wallet on tangem.com</string>

View file

@ -1,13 +1,17 @@
package com.tangem.feature.referral.converters
import android.text.format.DateUtils
import com.tangem.datasource.api.tangemTech.models.ReferralResponse
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.domain.models.TokenData
import com.tangem.feature.referral.domain.models.*
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.isToday
import com.tangem.utils.extensions.isYesterday
import com.tangem.utils.safeValueOf
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.format.DateTimeFormatterBuilder
import org.joda.time.format.ISODateTimeFormat
import java.util.Locale
import javax.inject.Inject
class ReferralConverter @Inject constructor() : Converter<ReferralResponse, ReferralData> {
@ -16,6 +20,7 @@ class ReferralConverter @Inject constructor() : Converter<ReferralResponse, Refe
val conditions = value.conditions
val referral = value.referral
val tokenConverter = TokenConverter()
val expectedAwardsConverter = ExpectedAwardsConverter()
return if (referral != null) {
ReferralData.ParticipantData(
award = conditions.awards.firstOrNull()?.amount ?: 0,
@ -32,6 +37,9 @@ class ReferralConverter @Inject constructor() : Converter<ReferralResponse, Refe
ISODateTimeFormat.dateTimeParser().parseDateTime(referral.termsAcceptedAt)
},
),
expectedAwards = value.expectedAwards?.let {
expectedAwardsConverter.convert(it)
},
)
} else {
ReferralData.NonParticipantData(
@ -45,6 +53,47 @@ class ReferralConverter @Inject constructor() : Converter<ReferralResponse, Refe
}
}
private class ExpectedAwardsConverter : Converter<ReferralResponse.ExpectedAwards, ExpectedAwards> {
/** Example, 2 Aug, 2023 */
private val dateFormatter by lazy {
DateTimeFormatterBuilder()
.appendDayOfMonth(1)
.appendLiteral(' ')
.appendMonthOfYearShortText()
.appendLiteral(", ")
.appendYear(4, 4)
.toFormatter()
.withLocale(Locale.getDefault())
}
override fun convert(value: ReferralResponse.ExpectedAwards): ExpectedAwards {
return ExpectedAwards(
numberOfWallets = value.numberOfWallets,
expectedAwards = value.list.map {
ExpectedAward(
paymentDate = it.paymentDate.toDateTimeAtStartOfDay().millis.toDateFormat(),
amount = "${it.amount} ${it.currency}",
)
},
)
}
private fun Long.toDateFormat(): String {
val localDate = DateTime(this, DateTimeZone.getDefault())
return if (localDate.isToday() || localDate.isYesterday()) {
DateUtils.getRelativeTimeSpanString(
this,
DateTime.now().millis,
DateUtils.DAY_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE,
).toString()
} else {
dateFormatter.print(localDate)
}
}
}
private class TokenConverter : Converter<ReferralResponse.Conditions.Award.Token, TokenData> {
override fun convert(value: ReferralResponse.Conditions.Award.Token): TokenData {

View file

@ -22,7 +22,7 @@ internal class ReferralRepositoryImpl @Inject constructor(
override val isDemoMode: Boolean
get() = demoModeDatasource.isDemoModeActive
override suspend fun getReferralStatus(walletId: String): ReferralData {
override suspend fun getReferralData(walletId: String): ReferralData {
return withContext(coroutineDispatcher.io) {
referralConverter.convert(
referralApi.getReferralStatus(

View file

@ -20,9 +20,11 @@ internal class ReferralInteractorImpl(
get() = repository.isDemoMode
override suspend fun getReferralStatus(): ReferralData {
val refStatus = repository.getReferralStatus(userWalletManager.getWalletId())
saveRefTokens(refStatus.tokens)
return refStatus
val referralData = repository.getReferralData(userWalletManager.getWalletId())
saveReferralTokens(referralData.tokens)
return referralData
}
override suspend fun startReferral(): ReferralData {
@ -37,7 +39,7 @@ internal class ReferralInteractorImpl(
address = publicAddress,
)
} else {
error("tokens for ref is empty")
error("Tokens for ref is empty")
}
}
@ -53,7 +55,7 @@ internal class ReferralInteractorImpl(
return derivationPath
}
private fun saveRefTokens(tokens: List<TokenData>) {
private fun saveReferralTokens(tokens: List<TokenData>) {
tokensForReferral.clear()
tokensForReferral.addAll(tokens)
}

View file

@ -7,7 +7,7 @@ interface ReferralRepository {
val isDemoMode: Boolean
/** Returns data object of [ReferralData] depends on user program status */
suspend fun getReferralStatus(walletId: String): ReferralData
suspend fun getReferralData(walletId: String): ReferralData
/** Starts user referral program */
suspend fun startReferral(walletId: String, networkId: String, tokenId: String, address: String): ReferralData

View file

@ -18,6 +18,7 @@ sealed interface ReferralData {
override val tosLink: String,
override val tokens: List<TokenData>,
val referral: ReferralInfo,
val expectedAwards: ExpectedAwards?,
) : ReferralData
/** Data class that used if user is not participant of program */
@ -47,6 +48,16 @@ data class ReferralInfo(
val termsAcceptedAt: DateTime?,
)
data class ExpectedAwards(
val numberOfWallets: Int,
val expectedAwards: List<ExpectedAward>,
)
data class ExpectedAward(
val paymentDate: String,
val amount: String,
)
enum class DiscountType {
PERCENTAGE, VALUE
}

View file

@ -1,5 +1,7 @@
package com.tangem.feature.referral.models
import com.tangem.feature.referral.domain.models.ExpectedAwards
internal data class ReferralStateHolder(
val headerState: HeaderState,
val referralInfoState: ReferralInfoState,
@ -26,6 +28,7 @@ internal data class ReferralStateHolder(
val code: String,
val shareLink: String,
override val url: String,
val expectedAwards: ExpectedAwards?,
) : ReferralInfoState, ReferralInfoContentState
data class NonParticipantContent(

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -43,13 +44,16 @@ internal fun AgreementBottomSheetContent(url: String) {
@Composable
private fun AgreementHtmlView(url: String) {
val state = rememberWebViewState(url)
val isInPreviewMode = LocalInspectionMode.current
WebView(
state = state,
modifier = Modifier.background(TangemTheme.colors.background.secondary),
onCreated = {
it.settings.apply {
javaScriptEnabled = false
allowFileAccess = false
if (!isInPreviewMode) {
it.settings.apply {
javaScriptEnabled = false
allowFileAccess = false
}
}
},
)

View file

@ -0,0 +1,71 @@
package com.tangem.feature.referral.ui
import androidx.compose.foundation.layout.*
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.res.TangemTheme
@Suppress("LongParameterList")
@Composable
internal fun AwardText(
startText: String,
startTextColor: Color,
startTextStyle: TextStyle,
endText: String,
endTextColor: Color,
endTextStyle: TextStyle,
cornersToRound: CornersToRound,
) {
Surface(
shape = cornersToRound.getShape(),
color = TangemTheme.colors.background.primary,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(TangemTheme.dimens.size48)
.padding(
horizontal = TangemTheme.dimens.spacing16,
vertical = TangemTheme.dimens.spacing12,
),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = startText,
color = startTextColor,
maxLines = 1,
style = startTextStyle,
)
Text(
text = endText,
color = endTextColor,
maxLines = 1,
style = endTextStyle,
)
}
}
}
@Preview(widthDp = 360, showBackground = true)
@Composable
private fun Preview_AwardItem_Light() {
TangemTheme {
AwardText(
startText = "startText",
startTextColor = TangemTheme.colors.text.tertiary,
startTextStyle = TangemTheme.typography.subtitle2,
endText = "endText",
endTextColor = TangemTheme.colors.text.primary1,
endTextStyle = TangemTheme.typography.subtitle2,
cornersToRound = CornersToRound.TOP_2,
)
}
}

View file

@ -0,0 +1,27 @@
package com.tangem.feature.referral.ui
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
internal enum class CornersToRound {
ALL_4,
TOP_2,
BOTTOM_2,
ZERO,
;
@Suppress("TopLevelComposableFunctions")
@Composable
fun getShape(): RoundedCornerShape {
val radius = TangemTheme.dimens.radius12
return when (this) {
ALL_4 -> RoundedCornerShape(radius)
TOP_2 -> RoundedCornerShape(topStart = radius, topEnd = radius)
BOTTOM_2 -> RoundedCornerShape(bottomStart = radius, bottomEnd = radius)
ZERO -> RoundedCornerShape(0.dp)
}
}
}

View file

@ -2,15 +2,19 @@ package com.tangem.feature.referral.ui
import android.content.Context
import android.content.Intent
import androidx.compose.animation.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@ -19,14 +23,16 @@ 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.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat.startActivity
import com.tangem.core.ui.components.PrimaryStartIconButton
import com.tangem.core.ui.components.SmallInfoCard
import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.referral.domain.models.ExpectedAward
import com.tangem.feature.referral.domain.models.ExpectedAwards
import com.tangem.feature.referral.presentation.R
@Suppress("LongParameterList")
@ -36,6 +42,7 @@ internal fun ParticipateBottomBlock(
purchasedWalletCount: Int,
code: String,
shareLink: String,
expectedAwards: ExpectedAwards?,
onAgreementClick: () -> Unit,
onShowCopySnackbar: () -> Unit,
onCopyClick: () -> Unit,
@ -50,14 +57,6 @@ internal fun ParticipateBottomBlock(
.padding(horizontal = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
) {
SmallInfoCard(
startText = stringResource(id = R.string.referral_friends_bought_title),
endText = pluralStringResource(
id = R.plurals.referral_wallets_purchased_count,
count = purchasedWalletCount,
purchasedWalletCount,
),
)
PersonalCodeCard(code = code)
AdditionalButtons(
code = code,
@ -66,10 +65,187 @@ internal fun ParticipateBottomBlock(
onCopyClick = onCopyClick,
onShareClick = onShareClick,
)
CounterAndAwards(purchasedWalletCount = purchasedWalletCount, expectedAwards = expectedAwards)
AgreementText(firstPartResId = R.string.referral_tos_enroled_prefix, onClick = onAgreementClick)
}
}
@Composable
private fun CounterAndAwards(purchasedWalletCount: Int, expectedAwards: ExpectedAwards?) {
Column {
Counter(purchasedWalletCount, expectedAwards)
if (expectedAwards != null) {
Awards(expectedAwards)
} else if (purchasedWalletCount != 0) {
EmptyUpcomingPayments()
}
}
}
@Composable
private fun Counter(purchasedWalletCount: Int, expectedAwards: ExpectedAwards?) {
val isExpectedAwardsPresent = expectedAwards != null
AwardText(
startText = stringResource(id = R.string.referral_friends_bought_title),
startTextColor = TangemTheme.colors.text.tertiary,
startTextStyle = TangemTheme.typography.subtitle2,
endText = pluralStringResource(
id = R.plurals.referral_wallets_purchased_count,
count = purchasedWalletCount,
purchasedWalletCount,
),
endTextColor = TangemTheme.colors.text.primary1,
endTextStyle = TangemTheme.typography.body2,
cornersToRound = if (isExpectedAwardsPresent || purchasedWalletCount != 0) {
CornersToRound.TOP_2
} else {
CornersToRound.ALL_4
},
)
}
@Suppress("MagicNumber")
@Composable
private fun Awards(expectedAwards: ExpectedAwards) {
val elementsCountToShowInLessMode = 3
val isExpanded = remember { mutableStateOf(false) }
Divider(
color = TangemTheme.colors.stroke.primary,
thickness = TangemTheme.dimens.size0_5,
)
AwardText(
startText = stringResource(id = R.string.referral_expected_awards),
startTextColor = TangemTheme.colors.text.tertiary,
startTextStyle = TangemTheme.typography.subtitle2,
endText = pluralStringResource(
id = R.plurals.referral_number_of_wallets,
count = expectedAwards.numberOfWallets,
expectedAwards.numberOfWallets,
),
endTextColor = TangemTheme.colors.text.tertiary,
endTextStyle = TangemTheme.typography.body2,
cornersToRound = CornersToRound.ZERO,
)
val initialItems = expectedAwards.expectedAwards.take(elementsCountToShowInLessMode)
val extraItems = expectedAwards.expectedAwards.drop(elementsCountToShowInLessMode)
initialItems.forEachIndexed { index, expectedAward ->
AwardText(
startText = expectedAward.paymentDate,
startTextColor = TangemTheme.colors.text.primary1,
startTextStyle = TangemTheme.typography.subtitle2,
endText = expectedAward.amount,
endTextColor = TangemTheme.colors.text.primary1,
endTextStyle = TangemTheme.typography.subtitle2,
cornersToRound = if (index == initialItems.size - 1 && extraItems.isEmpty()) {
CornersToRound.BOTTOM_2
} else {
CornersToRound.ZERO
},
)
}
AnimatedVisibility(
visible = isExpanded.value,
enter = fadeIn() + expandVertically(),
exit = shrinkVertically() + fadeOut(),
) {
ExtraItems(extraItems = extraItems)
}
if (expectedAwards.expectedAwards.size > elementsCountToShowInLessMode) {
LessMoreButton(isExpanded = isExpanded)
}
}
@Composable
private fun EmptyUpcomingPayments() {
Divider(
color = TangemTheme.colors.stroke.primary,
thickness = TangemTheme.dimens.size0_5,
)
AwardText(
startText = stringResource(id = R.string.referral_expected_awards),
startTextColor = TangemTheme.colors.text.tertiary,
startTextStyle = TangemTheme.typography.subtitle2,
endText = "",
endTextColor = TangemTheme.colors.text.tertiary,
endTextStyle = TangemTheme.typography.body2,
cornersToRound = CornersToRound.BOTTOM_2,
)
}
@Composable
private fun LessMoreButton(isExpanded: MutableState<Boolean>) {
Surface(
shape = RoundedCornerShape(
bottomStart = TangemTheme.dimens.radius12,
bottomEnd = TangemTheme.dimens.radius12,
),
) {
Column(
modifier = Modifier.background(TangemTheme.colors.background.primary),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(TangemTheme.dimens.size48)
.clickable { isExpanded.value = !isExpanded.value }
.padding(
horizontal = TangemTheme.dimens.spacing16,
vertical = TangemTheme.dimens.spacing12,
),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = if (isExpanded.value) {
stringResource(id = R.string.referral_less)
} else {
stringResource(id = R.string.referral_more)
},
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
val chevronIcon = if (isExpanded.value) {
painterResource(id = com.tangem.core.ui.R.drawable.ic_chevron_up_24)
} else {
painterResource(id = com.tangem.core.ui.R.drawable.ic_chevron_24)
}
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
painter = chevronIcon,
tint = TangemTheme.colors.text.tertiary,
contentDescription = null,
)
}
}
}
}
@Composable
private fun ExtraItems(extraItems: List<ExpectedAward>) {
Column {
extraItems.forEach { expectedAward ->
AwardText(
startText = expectedAward.paymentDate,
startTextColor = TangemTheme.colors.text.primary1,
startTextStyle = TangemTheme.typography.subtitle2,
endText = expectedAward.amount,
endTextColor = TangemTheme.colors.text.primary1,
endTextStyle = TangemTheme.typography.subtitle2,
cornersToRound = CornersToRound.ZERO,
)
}
}
}
@Composable
private fun PersonalCodeCard(code: String) {
Column(
@ -157,11 +333,28 @@ private fun Context.shareText(text: String) {
@Composable
private fun Preview_ParticipateBottomBlock_InLightTheme() {
TangemTheme(isDark = false) {
Column(Modifier.background(TangemTheme.colors.background.primary)) {
Column(Modifier.background(TangemTheme.colors.background.secondary)) {
ParticipateBottomBlock(
purchasedWalletCount = 3,
code = "x4JdK",
shareLink = "",
expectedAwards = ExpectedAwards(
numberOfWallets = 3,
expectedAwards = listOf(
ExpectedAward(
amount = "10 USDT",
paymentDate = "Today",
),
ExpectedAward(
amount = "20 USDT",
paymentDate = "6 Aug 2023",
),
ExpectedAward(
amount = "30 USDT",
paymentDate = "10 Aug 2023",
),
),
),
onAgreementClick = {},
onShowCopySnackbar = {},
onCopyClick = {},
@ -173,13 +366,64 @@ private fun Preview_ParticipateBottomBlock_InLightTheme() {
@Preview(widthDp = 360, showBackground = true)
@Composable
private fun Preview_ParticipateBottomBlock_InDarkTheme() {
TangemTheme(isDark = true) {
Column(Modifier.background(TangemTheme.colors.background.primary)) {
private fun Preview_ParticipateBottomBlock_Without_Awards_InLightTheme() {
TangemTheme(isDark = false) {
Column(Modifier.background(TangemTheme.colors.background.secondary)) {
ParticipateBottomBlock(
purchasedWalletCount = 3,
code = "x4JdK",
shareLink = "",
expectedAwards = null,
onAgreementClick = {},
onShowCopySnackbar = {},
onCopyClick = {},
onShareClick = {},
)
}
}
}
@Preview(widthDp = 360, showBackground = true)
@Composable
private fun Preview_ParticipateBottomBlock_Without_Awards_And_Purchased_Wallets_InLightTheme() {
TangemTheme(isDark = false) {
Column(Modifier.background(TangemTheme.colors.background.secondary)) {
ParticipateBottomBlock(
purchasedWalletCount = 0,
code = "x4JdK",
shareLink = "",
expectedAwards = null,
onAgreementClick = {},
onShowCopySnackbar = {},
onCopyClick = {},
onShareClick = {},
)
}
}
}
@Preview(widthDp = 360, showBackground = true)
@Composable
private fun LessMoreButton_White() {
TangemTheme(isDark = false) {
LessMoreButton(
isExpanded = remember {
mutableStateOf(false)
},
)
}
}
@Preview(widthDp = 360, showBackground = true)
@Composable
private fun Preview_ParticipateBottomBlock_Without_Awards_InDarkTheme() {
TangemTheme(isDark = true) {
Column(Modifier.background(TangemTheme.colors.background.secondary)) {
ParticipateBottomBlock(
purchasedWalletCount = 3,
code = "x4JdK",
shareLink = "",
expectedAwards = null,
onAgreementClick = {},
onShowCopySnackbar = {},
onCopyClick = {},

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
@ -31,6 +32,8 @@ import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.referral.domain.models.ExpectedAward
import com.tangem.feature.referral.domain.models.ExpectedAwards
import com.tangem.feature.referral.models.DemoModeException
import com.tangem.feature.referral.models.ReferralStateHolder
import com.tangem.feature.referral.models.ReferralStateHolder.*
@ -159,6 +162,7 @@ private fun ReferralInfo(
purchasedWalletCount = state.purchasedWalletCount,
code = state.code,
shareLink = state.shareLink,
expectedAwards = state.expectedAwards,
onAgreementClick = onAgreementClick,
onShowCopySnackbar = onShowCopySnackbar,
onCopyClick = stateHolder.analytics.onCopyClicked,
@ -253,25 +257,47 @@ private fun Condition(@DrawableRes iconResId: Int, infoBlock: @Composable () ->
private fun InfoForYou(award: String, networkName: String, address: String? = null) {
ConditionInfo(title = stringResource(id = R.string.referral_point_currencies_title)) {
Text(
text = buildAnnotatedString {
append(stringResource(id = R.string.referral_point_currencies_description_prefix))
withStyle(SpanStyle(color = TangemTheme.colors.text.primary1)) {
append(" $award ")
}
append(
String.format(
stringResource(id = R.string.referral_point_currencies_description_suffix),
networkName,
if (!address.isNullOrBlank()) " $address" else "",
),
)
},
formatAwardConditionsString(
quantity = award,
network = networkName,
address = if (!address.isNullOrBlank()) " $address" else "",
),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
)
}
}
@Composable
private fun formatAwardConditionsString(quantity: String, network: String, address: String): AnnotatedString {
val rawString = stringResource(R.string.referral_point_currencies_description, quantity, network, address)
val pattern = Regex("\\^\\^(.*?)\\^\\^")
var startIndex = 0
val annotatedString = buildAnnotatedString {
pattern.findAll(rawString).forEach { matchResult ->
val index = matchResult.range.first
val matchedValue = matchResult.groups[1]?.value ?: ""
// appends unformatted part
append(rawString.substring(startIndex, index))
// applies style on ^^-wrapped parts
withStyle(SpanStyle(color = TangemTheme.colors.text.primary1)) {
append(matchedValue)
}
// goes to next part
startIndex = matchResult.range.last + 1
}
// appends remaining ending if exists
append(rawString.substring(startIndex))
}
return annotatedString
}
@Composable
private fun InfoForYourFriend(discount: String) {
ConditionInfo(title = stringResource(id = R.string.referral_point_discount_title)) {
@ -464,6 +490,7 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() {
code = "x4JdK",
shareLink = "",
url = "",
expectedAwards = null,
),
errorSnackbar = null,
analytics = Analytics(
@ -492,6 +519,52 @@ private fun Preview_ReferralScreen_Participant_InDarkTheme() {
code = "x4JdK",
shareLink = "",
url = "",
expectedAwards = null,
),
errorSnackbar = null,
analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
onShareClicked = {},
),
),
)
}
}
@Preview(widthDp = 360, showBackground = true)
@Composable
private fun Preview_ReferralScreen_Participant_With_Referrals_InLightTheme() {
TangemTheme(isDark = false) {
ReferralScreen(
stateHolder = ReferralStateHolder(
headerState = HeaderState(onBackClicked = {}),
referralInfoState = ReferralInfoState.ParticipantContent(
award = "10 USDT",
networkName = "Tron",
address = "ma80...zk8q2",
discount = "10%",
purchasedWalletCount = 3,
code = "x4JdK",
shareLink = "",
url = "",
expectedAwards = ExpectedAwards(
numberOfWallets = 5,
expectedAwards = listOf(
ExpectedAward(
amount = "10 USDT",
paymentDate = "Today",
),
ExpectedAward(
amount = "20 USDT",
paymentDate = "6 Aug 2023",
),
ExpectedAward(
amount = "30 USDT",
paymentDate = "10 Aug 2023",
),
),
),
),
errorSnackbar = null,
analytics = Analytics(

View file

@ -36,7 +36,7 @@ internal class ReferralViewModel @Inject constructor(
private var referralRouter: ReferralRouter by Delegates.notNull()
private val lastReferralData = mutableStateOf<ReferralData?>(null)
private var lastReferralData: ReferralData? = null
init {
loadReferralData()
@ -67,7 +67,7 @@ internal class ReferralViewModel @Inject constructor(
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
referralInteractor.getReferralStatus().apply {
lastReferralData.value = this
lastReferralData = this
}
}
.onSuccess(::showContent)
@ -84,14 +84,13 @@ internal class ReferralViewModel @Inject constructor(
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) { referralInteractor.startReferral() }
.onSuccess(::showContent)
.onFailure {
if (it is UserCancelledException) {
val lastRefData = lastReferralData.value
if (lastRefData != null) {
showContent(lastRefData)
.onFailure { throwable ->
if (throwable is UserCancelledException) {
lastReferralData?.let { referralData ->
showContent(referralData)
}
} else {
showErrorSnackbar(it)
showErrorSnackbar(throwable)
}
}
}
@ -130,6 +129,7 @@ internal class ReferralViewModel @Inject constructor(
code = referral.promocode,
shareLink = referral.shareLink,
url = tosLink,
expectedAwards = expectedAwards,
)
is ReferralData.NonParticipantData -> ReferralInfoState.NonParticipantContent(
award = getAwardValue(),