1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-2274 Payment | Welcome screen (#1000)

This commit is contained in:
Konstantin Ivanov 2024-03-14 13:21:13 +01:00 committed by GitHub
parent 7d65104c34
commit d123867aaf
Signed by: github
GPG key ID: B5690EEEBB952194
12 changed files with 536 additions and 201 deletions

View file

@ -1,31 +1,36 @@
package com.anytypeio.anytype.models
import com.anytypeio.anytype.viewmodel.TierId
sealed class Tier {
abstract val id: String
abstract val id: TierId
abstract val isCurrent: Boolean
abstract val validUntil: String
data class Explorer(
override val id: String,
override val id: TierId,
override val isCurrent: Boolean,
override val validUntil: String,
val price: String = "",
val email: String = "",
val isChecked: Boolean = true
) : Tier()
data class Builder(
override val id: String,
override val id: TierId,
override val isCurrent: Boolean,
override val validUntil: String,
val price: String = "",
val interval: String = "",
val name: String = "",
val nameIsTaken: Boolean = false,
val nameIsFree: Boolean = false
) : Tier()
data class CoCreator(
override val id: String,
override val id: TierId,
override val isCurrent: Boolean,
override val validUntil: String,
val price: String = "",
val interval: String = "",
val name: String = "",
@ -34,8 +39,9 @@ sealed class Tier {
) : Tier()
data class Custom(
override val id: String,
override val id: TierId,
override val isCurrent: Boolean,
override val validUntil: String,
val price: String = ""
) : Tier()
}

View file

@ -52,38 +52,42 @@ import com.anytypeio.anytype.core_ui.views.HeadlineTitle
import com.anytypeio.anytype.core_ui.views.PreviewTitle1Regular
import com.anytypeio.anytype.peyments.R
import com.anytypeio.anytype.viewmodel.PaymentsCodeState
import com.anytypeio.anytype.viewmodel.TierId
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CodeScreen(
state: PaymentsCodeState,
actionResend: () -> Unit,
actionCode: (String) -> Unit,
actionCode: (String, TierId) -> Unit,
onDismiss: () -> Unit
) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = onDismiss,
containerColor = colorResource(id = R.color.background_primary),
content = { ModalCodeContent(state = state, actionCode = actionCode) }
)
if (state is PaymentsCodeState.Visible) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = onDismiss,
containerColor = colorResource(id = R.color.background_primary),
content = { ModalCodeContent(state = state, actionCode = { code -> actionCode(code, state.tierId)}) }
)
}
}
@Composable
private fun ModalCodeContent(state: PaymentsCodeState, actionCode: (String) -> Unit) {
private fun ModalCodeContent(state: PaymentsCodeState.Visible, actionCode: (String) -> Unit) {
val focusRequesters = remember { List(4) { FocusRequester() } }
val enteredDigits = remember { mutableStateListOf<Char>() }
val focusManager = LocalFocusManager.current
LaunchedEffect(key1 = enteredDigits.size) {
if (enteredDigits.size == 4) {
actionCode(enteredDigits.joinToString(""))
val code = enteredDigits.joinToString("")
actionCode(code)
}
}
LaunchedEffect(key1 = state) {
if (state is PaymentsCodeState.Loading) {
if (state is PaymentsCodeState.Visible.Loading) {
focusManager.clearFocus(true)
}
}
@ -114,7 +118,7 @@ private fun ModalCodeContent(state: PaymentsCodeState, actionCode: (String) -> U
) {
focusRequesters.forEachIndexed { index, focusRequester ->
CodeNumber(
isEnabled = state !is PaymentsCodeState.Loading,
isEnabled = state !is PaymentsCodeState.Visible.Loading,
modifier = modifier,
focusRequester = focusRequester,
onDigitEntered = { digit ->
@ -131,7 +135,7 @@ private fun ModalCodeContent(state: PaymentsCodeState, actionCode: (String) -> U
if (index < 3) Spacer(modifier = Modifier.width(8.dp))
}
}
if (state is PaymentsCodeState.Error) {
if (state is PaymentsCodeState.Visible.Error) {
Text(
text = state.message,
color = colorResource(id = R.color.palette_system_red),
@ -152,7 +156,7 @@ private fun ModalCodeContent(state: PaymentsCodeState, actionCode: (String) -> U
}
AnimatedVisibility(
modifier = Modifier.align(Alignment.Center),
visible = state is PaymentsCodeState.Loading,
visible = state is PaymentsCodeState.Visible.Loading,
enter = fadeIn(),
exit = fadeOut()
) {
@ -227,7 +231,7 @@ private fun CodeNumber(
@Composable
fun EnterCodeModalPreview() {
ModalCodeContent(
state = PaymentsCodeState.Loading,
actionCode = {}
state = PaymentsCodeState.Visible.Loading(TierId("123")),
actionCode = { _ -> }
)
}

View file

@ -54,10 +54,11 @@ import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.Relations2
import com.anytypeio.anytype.core_ui.views.fontRiccioneRegular
import com.anytypeio.anytype.models.Tier
import com.anytypeio.anytype.viewmodel.PaymentsState
import com.anytypeio.anytype.viewmodel.PaymentsMainState
import com.anytypeio.anytype.viewmodel.TierId
@Composable
fun MainPaymentsScreen(state: PaymentsState, tierClicked: (Tier) -> Unit) {
fun MainPaymentsScreen(state: PaymentsMainState, tierClicked: (TierId) -> Unit) {
Box(
modifier = Modifier
.nestedScroll(rememberNestedScrollInteropConnection())
@ -74,10 +75,13 @@ fun MainPaymentsScreen(state: PaymentsState, tierClicked: (Tier) -> Unit) {
.padding(bottom = 20.dp)
.verticalScroll(rememberScrollState())
) {
if (state is PaymentsState.Default) {
Header()
if (state is PaymentsMainState.Default) {
Title()
Spacer(modifier = Modifier.height(7.dp))
Subtitle()
Spacer(modifier = Modifier.height(32.dp))
InfoCards()
Spacer(modifier = Modifier.height(32.dp))
TiersList(tiers = state.tiers, tierClicked = tierClicked)
Spacer(modifier = Modifier.height(32.dp))
LinkButton(text = stringResource(id = R.string.payments_member_link), action = {})
@ -88,9 +92,9 @@ fun MainPaymentsScreen(state: PaymentsState, tierClicked: (Tier) -> Unit) {
Spacer(modifier = Modifier.height(32.dp))
BottomText()
}
if (state is PaymentsState.PaymentSuccess) {
Header()
Spacer(modifier = Modifier.height(32.dp))
if (state is PaymentsMainState.PaymentSuccess) {
Title()
Spacer(modifier = Modifier.height(39.dp))
TiersList(tiers = state.tiers, tierClicked = tierClicked)
Spacer(modifier = Modifier.height(32.dp))
LinkButton(text = stringResource(id = R.string.payments_member_link), action = {})
@ -106,7 +110,7 @@ fun MainPaymentsScreen(state: PaymentsState, tierClicked: (Tier) -> Unit) {
}
@Composable
private fun Header() {
private fun Title() {
// Dragger at the top, centered
Box(
@ -133,7 +137,10 @@ private fun Header() {
textAlign = TextAlign.Center
)
}
}
@Composable
private fun Subtitle() {
Box(
modifier = Modifier
.fillMaxWidth()
@ -142,7 +149,7 @@ private fun Header() {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(start = 60.dp, end = 60.dp, top = 7.dp),
.padding(start = 60.dp, end = 60.dp),
text = stringResource(id = R.string.payments_subheader),
color = colorResource(id = R.color.text_primary),
style = Relations2,
@ -153,13 +160,12 @@ private fun Header() {
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun TiersList(tiers: List<Tier>, tierClicked: (Tier) -> Unit) {
fun TiersList(tiers: List<Tier>, tierClicked: (TierId) -> Unit) {
val itemsScroll = rememberLazyListState(initialFirstVisibleItemIndex = 1)
LazyRow(
state = itemsScroll,
modifier = Modifier
.fillMaxWidth()
.padding(top = 32.dp),
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(20.dp),
contentPadding = PaddingValues(start = 20.dp, end = 20.dp),
flingBehavior = rememberSnapFlingBehavior(lazyListState = itemsScroll)
@ -174,7 +180,8 @@ fun TiersList(tiers: List<Tier>, tierClicked: (Tier) -> Unit) {
radialGradient = resources.radialGradient,
icon = resources.smallIcon,
buttonText = stringResource(id = R.string.payments_button_learn),
onClick = { tierClicked.invoke(tier) }
onClick = { tierClicked.invoke(tier.id) },
isCurrent = tier.isCurrent
)
}
}
@ -281,12 +288,12 @@ fun BottomText() {
@Composable
fun MainPaymentsScreenPreview() {
val tiers = listOf(
Tier.Explorer("999", isCurrent = true),
Tier.Builder("999", isCurrent = false),
Tier.CoCreator("999", isCurrent = false),
Tier.Custom("999", isCurrent = false)
Tier.Explorer(TierId("999"), isCurrent = true, validUntil = "2022-12-31"),
Tier.Builder(TierId("999"), isCurrent = true, validUntil = "2022-12-31"),
Tier.CoCreator(TierId("999"), isCurrent = false, validUntil = "2022-12-31"),
Tier.Custom(TierId("999"), isCurrent = false, validUntil = "2022-12-31")
)
MainPaymentsScreen(PaymentsState.PaymentSuccess(tiers), {})
MainPaymentsScreen(PaymentsMainState.PaymentSuccess(tiers), {})
}
val headerTextStyle = TextStyle(

View file

@ -52,32 +52,37 @@ import com.anytypeio.anytype.core_ui.views.BodyBold
import com.anytypeio.anytype.core_ui.views.BodyCallout
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSecondary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.HeadlineTitle
import com.anytypeio.anytype.core_ui.views.Relations1
import com.anytypeio.anytype.core_ui.views.Relations2
import com.anytypeio.anytype.models.Tier
import com.anytypeio.anytype.peyments.R
import com.anytypeio.anytype.viewmodel.PaymentsTierState
import com.anytypeio.anytype.viewmodel.TierId
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TierScreen(tier: Tier?, onDismiss: () -> Unit, actionPay: () -> Unit) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
modifier = Modifier.padding(top = 30.dp),
sheetState = sheetState,
containerColor = Color.Transparent,
dragHandle = null,
onDismissRequest = { onDismiss() },
content = {
MembershipLevels(tier = tier, actionPay = actionPay)
}
)
fun TierScreen(state: PaymentsTierState, onDismiss: () -> Unit, actionPay: (TierId) -> Unit) {
if (state is PaymentsTierState.Visible) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
modifier = Modifier.padding(top = 30.dp),
sheetState = sheetState,
containerColor = Color.Transparent,
dragHandle = null,
onDismissRequest = { onDismiss() },
content = {
MembershipLevels(tier = state.tier, actionPay = { actionPay(state.tier.id) })
}
)
}
}
@Composable
fun MembershipLevels(tier: Tier?, actionPay: () -> Unit) {
fun MembershipLevels(tier: Tier, actionPay: () -> Unit) {
Box(
modifier = Modifier
@ -153,29 +158,32 @@ fun MembershipLevels(tier: Tier?, actionPay: () -> Unit) {
})
}
if (tier is Tier.Builder) {
NamePickerAndButton(
name = tier.name,
nameIsTaken = tier.nameIsTaken,
nameIsFree = tier.nameIsFree,
price = tier.price,
interval = tier.interval,
actionPay = actionPay
)
if (tier.isCurrent) {
StatusSubscribed(tier, {})
} else {
NamePickerAndButton(
name = tier.name,
nameIsTaken = tier.nameIsTaken,
nameIsFree = tier.nameIsFree,
price = tier.price,
interval = tier.interval,
actionPay = actionPay
)
}
}
if (tier is Tier.CoCreator) {
NamePickerAndButton(
name = tier.name,
nameIsTaken = tier.nameIsTaken,
nameIsFree = tier.nameIsFree,
price = tier.price,
interval = tier.interval,
actionPay = actionPay
)
Price(tier.price, tier.interval)
Spacer(modifier = Modifier.height(14.dp))
ButtonPay(enabled = true, actionPay = {
})
if (tier.isCurrent) {
StatusSubscribed(tier, {})
} else {
NamePickerAndButton(
name = tier.name,
nameIsTaken = tier.nameIsTaken,
nameIsFree = tier.nameIsFree,
price = tier.price,
interval = tier.interval,
actionPay = actionPay
)
}
}
}
}
@ -211,7 +219,7 @@ fun NamePickerAndButton(
modifier = Modifier
.fillMaxWidth()
.padding(top = 26.dp),
text = stringResource(id = R.string.payments_details_name_title),
text = stringResource(id = R.string.payments_tier_details_name_title),
color = colorResource(id = R.color.text_primary),
style = BodyBold,
textAlign = TextAlign.Start
@ -220,7 +228,7 @@ fun NamePickerAndButton(
modifier = Modifier
.fillMaxWidth()
.padding(top = 6.dp),
text = stringResource(id = R.string.payments_details_name_subtitle),
text = stringResource(id = R.string.payments_tier_details_name_subtitle),
color = colorResource(id = R.color.text_primary),
style = BodyCallout,
textAlign = TextAlign.Start
@ -256,7 +264,7 @@ fun NamePickerAndButton(
decorationBox = { innerTextField ->
if (innerValue.isEmpty()) {
Text(
text = stringResource(id = com.anytypeio.anytype.localization.R.string.payments_details_name_hint),
text = stringResource(id = com.anytypeio.anytype.localization.R.string.payments_tier_details_name_hint),
style = BodyRegular,
color = colorResource(id = com.anytypeio.anytype.core_ui.R.color.text_tertiary),
modifier = Modifier
@ -268,7 +276,7 @@ fun NamePickerAndButton(
}
)
Text(
text = stringResource(id = R.string.payments_details_name_domain),
text = stringResource(id = R.string.payments_tier_details_name_domain),
style = BodyRegular,
color = colorResource(id = R.color.text_primary)
)
@ -277,13 +285,13 @@ fun NamePickerAndButton(
Divider(paddingStart = 0.dp, paddingEnd = 0.dp)
val (messageTextColor, messageText) = when {
nameIsTaken ->
colorResource(id = R.color.palette_system_red) to stringResource(id = R.string.payments_details_name_error)
colorResource(id = R.color.palette_system_red) to stringResource(id = R.string.payments_tier_details_name_error)
nameIsFree ->
colorResource(id = R.color.palette_dark_lime) to stringResource(id = R.string.payments_details_name_success)
colorResource(id = R.color.palette_dark_lime) to stringResource(id = R.string.payments_tier_details_name_success)
else ->
colorResource(id = R.color.text_secondary) to stringResource(id = R.string.payments_details_name_min)
colorResource(id = R.color.text_secondary) to stringResource(id = R.string.payments_tier_details_name_min)
}
Spacer(modifier = Modifier.height(10.dp))
Text(
@ -303,6 +311,80 @@ fun NamePickerAndButton(
}
}
@Composable
private fun StatusSubscribed(tier: Tier, actionManage: () -> Unit) {
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.background(
shape = RoundedCornerShape(16.dp),
color = colorResource(id = R.color.background_primary)
)
.padding(start = 20.dp, end = 20.dp)
) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = 26.dp),
text = stringResource(id = R.string.payments_tier_current_title),
color = colorResource(id = R.color.text_primary),
style = BodyBold,
textAlign = TextAlign.Start
)
Spacer(modifier = Modifier.height(14.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.background(
shape = RoundedCornerShape(12.dp),
color = colorResource(id = R.color.payments_tier_current_background)
)
) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = 34.dp),
text = stringResource(id = R.string.payments_tier_current_valid),
color = colorResource(id = R.color.text_primary),
style = Relations2,
textAlign = TextAlign.Center
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp),
text = tier.validUntil,
color = colorResource(id = R.color.text_primary),
style = HeadlineTitle,
textAlign = TextAlign.Center
)
Text(
modifier = Modifier
.fillMaxWidth()
.padding(top = 23.dp, bottom = 15.dp),
text = stringResource(id = R.string.payments_tier_current_paid_by),
color = colorResource(id = R.color.text_secondary),
style = Relations2,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.height(20.dp))
ButtonSecondary(
enabled = true,
text = stringResource(id = R.string.payments_tier_current_button),
onClick = { actionManage() },
size = ButtonSize.LargeSecondary,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
)
}
}
@Composable
private fun Price(price: String, interval: String) {
Row() {
@ -485,10 +567,11 @@ private fun ButtonPay(enabled: Boolean, actionPay: () -> Unit) {
fun MyLevel() {
MembershipLevels(
tier = Tier.Builder(
id = "121",
id = TierId("121"),
isCurrent = true,
price = "$99",
interval = "per year"
interval = "per year",
validUntil = "12/12/2025",
),
actionPay = {}
)

View file

@ -0,0 +1,109 @@
package com.anytypeio.anytype.screens
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.anytypeio.anytype.core_ui.views.BodyRegular
import com.anytypeio.anytype.core_ui.views.ButtonSecondary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.HeadlineHeading
import com.anytypeio.anytype.models.Tier
import com.anytypeio.anytype.peyments.R
import com.anytypeio.anytype.viewmodel.PaymentsWelcomeState
import com.anytypeio.anytype.viewmodel.TierId
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PaymentWelcomeScreen(state: PaymentsWelcomeState, onDismiss: () -> Unit) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
if (state is PaymentsWelcomeState.Initial) {
ModalBottomSheet(
modifier = Modifier
.padding(start = 16.dp, end = 16.dp)
.fillMaxWidth()
.wrapContentHeight(),
sheetState = sheetState,
onDismissRequest = onDismiss,
containerColor = colorResource(id = R.color.background_secondary),
content = {
val tierResources = mapTierToResources(state.tier)
if (tierResources != null) WelcomeContent(tierResources, onDismiss)
},
shape = RoundedCornerShape(16.dp),
dragHandle = null
)
}
}
@Composable
private fun WelcomeContent(tierResources: TierResources, onDismiss: () -> Unit) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(36.dp))
Icon(
modifier = Modifier.wrapContentSize(),
painter = painterResource(id = tierResources.mediumIcon!!),
contentDescription = "logo",
tint = tierResources.radialGradient
)
Spacer(modifier = Modifier.height(14.dp))
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
text = stringResource(id = R.string.payments_welcome_title, tierResources.title),
color = colorResource(id = R.color.text_primary),
style = HeadlineHeading,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(7.dp))
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
text = stringResource(id = R.string.payments_welcome_subtitle),
color = colorResource(id = R.color.text_primary),
style = BodyRegular,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(30.dp))
ButtonSecondary(
text = stringResource(id = R.string.payments_welcome_button),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
onClick = { onDismiss() },
size = ButtonSize.LargeSecondary
)
Spacer(modifier = Modifier.height(16.dp))
}
}
@Preview
@Composable
fun PaymentWelcomeScreenPreview() {
PaymentWelcomeScreen(
PaymentsWelcomeState.Initial(Tier.Explorer(TierId("Free"), true, "01-01-2025")), {})
}

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@ -9,10 +10,12 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
@ -32,6 +35,7 @@ import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
import com.anytypeio.anytype.core_ui.views.ButtonSize
import com.anytypeio.anytype.core_ui.views.Caption1Regular
import com.anytypeio.anytype.core_ui.views.Relations3
import com.anytypeio.anytype.core_ui.views.fontInterSemibold
import com.anytypeio.anytype.models.Tier
@ -43,7 +47,8 @@ fun TierView(
radialGradient: Color,
icon: Int,
buttonText: String,
onClick: () -> Unit
onClick: () -> Unit,
isCurrent: Boolean
) {
val brush = Brush.verticalGradient(
listOf(
@ -52,60 +57,81 @@ fun TierView(
)
)
Column(
modifier = Modifier
.width(192.dp)
.wrapContentHeight()
.background(
color = colorResource(id = R.color.shape_tertiary),
shape = RoundedCornerShape(16.dp)
)
.noRippleThrottledClickable { onClick() }
) {
Box(
Box(modifier = Modifier.wrapContentSize()) {
Column(
modifier = Modifier
.fillMaxWidth()
.height(80.dp)
.background(brush = brush, shape = RoundedCornerShape(16.dp)),
contentAlignment = androidx.compose.ui.Alignment.BottomStart
.width(192.dp)
.wrapContentHeight()
.background(
color = colorResource(id = R.color.shape_tertiary),
shape = RoundedCornerShape(16.dp)
)
.noRippleThrottledClickable { onClick() }
) {
Icon(
Box(
modifier = Modifier
.padding(start = 16.dp),
painter = painterResource(id = icon),
contentDescription = "logo",
tint = radialGradient
.fillMaxWidth()
.height(80.dp)
.background(brush = brush, shape = RoundedCornerShape(16.dp)),
contentAlignment = Alignment.BottomStart
) {
Icon(
modifier = Modifier
.padding(start = 16.dp),
painter = painterResource(id = icon),
contentDescription = "logo",
tint = radialGradient
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.padding(start = 17.dp, top = 10.dp),
text = title,
color = colorResource(id = R.color.text_primary),
style = titleTextStyle,
textAlign = TextAlign.Start
)
Text(
modifier = Modifier
.fillMaxWidth()
.height(96.dp)
.padding(start = 16.dp, end = 16.dp, top = 5.dp),
text = subTitle,
color = colorResource(id = R.color.text_primary),
style = Caption1Regular,
textAlign = TextAlign.Start
)
PriceOrOption()
ButtonPrimary(
text = buttonText,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
onClick = { onClick() },
size = ButtonSize.Small
)
Spacer(modifier = Modifier.height(10.dp))
}
if (isCurrent) {
Text(
modifier = Modifier
.wrapContentSize()
.padding(end = 15.5.dp, top = 18.dp)
.border(
shape = RoundedCornerShape(11.dp),
color = colorResource(id = R.color.text_primary),
width = 1.dp
)
.align(Alignment.TopEnd)
.padding(top = 2.dp, bottom = 3.dp, start = 8.dp, end = 8.dp),
text = stringResource(id = R.string.payments_current_label),
color = colorResource(id = R.color.text_primary),
style = Relations3,
textAlign = TextAlign.Center
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.padding(start = 17.dp, top = 10.dp),
text = title,
color = colorResource(id = R.color.text_primary),
style = titleTextStyle,
textAlign = TextAlign.Start
)
Text(
modifier = Modifier
.fillMaxWidth()
.height(96.dp)
.padding(start = 16.dp, end = 16.dp, top = 5.dp),
text = subTitle,
color = colorResource(id = R.color.text_primary),
style = Caption1Regular,
textAlign = TextAlign.Start
)
PriceOrOption()
ButtonPrimary(
text = buttonText,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
onClick = { /*TODO*/ },
size = ButtonSize.Small
)
Spacer(modifier = Modifier.height(10.dp))
}
}
@ -175,7 +201,8 @@ fun TierPreview() {
onClick = {},
icon = R.drawable.logo_co_creator_64,
colorGradient = Color(0xFFCFF6CF),
radialGradient = Color(0xFF24BFD4)
radialGradient = Color(0xFF24BFD4),
isCurrent = true
)
}

View file

@ -0,0 +1,49 @@
package com.anytypeio.anytype.viewmodel
import com.anytypeio.anytype.models.Tier
sealed class PaymentsMainState {
object Loading : PaymentsMainState()
data class Default(val tiers: List<Tier>) : PaymentsMainState()
data class PaymentSuccess(val tiers: List<Tier>) : PaymentsMainState()
}
sealed class PaymentsTierState {
object Hidden : PaymentsTierState()
sealed class Visible : PaymentsTierState() {
abstract val tier: Tier
data class Initial(override val tier: Tier) : Visible()
data class Subscribed(override val tier: Tier) : Visible()
}
}
sealed class PaymentsCodeState {
object Hidden : PaymentsCodeState()
sealed class Visible : PaymentsCodeState() {
abstract val tierId: TierId
data class Initial(override val tierId: TierId) : Visible()
data class Loading(override val tierId: TierId) : Visible()
data class Success(override val tierId: TierId) : Visible()
data class Error(override val tierId: TierId, val message: String) : Visible()
}
}
sealed class PaymentsWelcomeState {
object Hidden : PaymentsWelcomeState()
data class Initial(val tier: Tier) : PaymentsWelcomeState()
}
sealed class PaymentsNavigation(val route: String) {
object Main : PaymentsNavigation("main")
object Tier : PaymentsNavigation("tier")
object Code : PaymentsNavigation("code")
object Welcome : PaymentsNavigation("welcome")
object Dismiss : PaymentsNavigation("")
}
@JvmInline
value class TierId(val value: String)

View file

@ -1,23 +0,0 @@
package com.anytypeio.anytype.viewmodel
import com.anytypeio.anytype.models.Tier
sealed class PaymentsState {
object Loading : PaymentsState()
data class Default(val tiers: List<Tier>) : PaymentsState()
data class PaymentSuccess(val tiers: List<Tier>) : PaymentsState()
}
sealed class PaymentsCodeState {
object Empty : PaymentsCodeState()
object Loading : PaymentsCodeState()
object Success : PaymentsCodeState()
data class Error(val message: String) : PaymentsCodeState()
}
sealed class PaymentsNavigation(val route: String) {
object Main : PaymentsNavigation("main")
object Tier : PaymentsNavigation("tier")
object Code : PaymentsNavigation("code")
object Dismiss : PaymentsNavigation("")
}

View file

@ -1,51 +1,93 @@
package com.anytypeio.anytype.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.models.Tier
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
class PaymentsViewModel(
private val analytics: Analytics,
) : ViewModel() {
val viewState = MutableStateFlow<PaymentsState>(PaymentsState.Loading)
val codeViewState = MutableStateFlow<PaymentsCodeState>(PaymentsCodeState.Empty)
val viewState = MutableStateFlow<PaymentsMainState>(PaymentsMainState.Loading)
val codeState = MutableStateFlow<PaymentsCodeState>(PaymentsCodeState.Hidden)
val tierState = MutableStateFlow<PaymentsTierState>(PaymentsTierState.Hidden)
val welcomeState = MutableStateFlow<PaymentsWelcomeState>(PaymentsWelcomeState.Hidden)
val command = MutableStateFlow<PaymentsNavigation?>(null)
val selectedTier = MutableStateFlow<Tier?>(null)
private val _tiers = mutableListOf<Tier>()
init {
Timber.d("PaymentsViewModel created")
viewState.value = PaymentsState.Default(
listOf(
Tier.Explorer("Free", true),
Tier.Builder("$9.99/mo", false),
Tier.CoCreator("$19.99/mo", false),
Tier.Custom("$29.99/mo", false)
)
)
Timber.d("PaymentsViewModel init")
_tiers.addAll(gertTiers())
viewState.value = PaymentsMainState.Default(_tiers)
}
fun onTierClicked(tier: Tier) {
selectedTier.value = tier
fun onTierClicked(tierId: TierId) {
Timber.d("onTierClicked: tierId:$tierId")
tierState.value = PaymentsTierState.Visible.Initial(tier = _tiers.first { it.id == tierId })
command.value = PaymentsNavigation.Tier
}
fun onActionCode(code: String) {
Timber.d("onActionCode: $code")
fun onActionCode(code: String, tierId: TierId) {
Timber.d("onActionCode: tierId:$tierId, code:$code, _tiers:${_tiers}")
viewModelScope.launch {
codeState.value = PaymentsCodeState.Visible.Loading(tierId = tierId)
delay(2000)
welcomeState.value =
PaymentsWelcomeState.Initial(tier = _tiers.first { it.id == tierId })
val updatedTiers = _tiers.map {
if (it.id == tierId) {
when (it) {
is Tier.Builder -> it.copy(isCurrent = true)
is Tier.CoCreator -> it.copy(isCurrent = true)
is Tier.Custom -> it.copy(isCurrent = true)
is Tier.Explorer -> it.copy(isCurrent = true)
}
} else {
it
}
}
_tiers.clear()
_tiers.addAll(updatedTiers)
viewState.value = PaymentsMainState.PaymentSuccess(_tiers)
command.value = PaymentsNavigation.Welcome
}
}
fun onPayButtonClicked() {
fun onPayButtonClicked(tierId: TierId) {
Timber.d("onPayButtonClicked: tierId:$tierId")
codeState.value = PaymentsCodeState.Visible.Initial(tierId = tierId)
command.value = PaymentsNavigation.Code
}
fun onDismissTier() {
Timber.d("onDismissTier")
command.value = PaymentsNavigation.Dismiss
}
fun onDismissCode() {
Timber.d("onDismissCode")
command.value = PaymentsNavigation.Dismiss
}
fun onDismissWelcome() {
Timber.d("onDismissWelcome")
command.value = PaymentsNavigation.Dismiss
}
private fun gertTiers(): List<Tier> {
return listOf(
Tier.Explorer(id = TierId("idExplorer"), isCurrent = false, validUntil = "2022-12-31"),
Tier.Builder(id = TierId("idBuilder"), isCurrent = false, validUntil = "2022-12-31"),
Tier.CoCreator(id = TierId("idCoCreator"), isCurrent = false, validUntil = "2022-12-31"),
Tier.Custom(id = TierId("idCustom"), isCurrent = false, validUntil = "2022-12-31")
)
}
}