diff --git a/app/src/main/java/com/anytypeio/anytype/ui/payments/PaymentsFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/payments/PaymentsFragment.kt
index faa9cd99a1..8881e2f7e1 100644
--- a/app/src/main/java/com/anytypeio/anytype/ui/payments/PaymentsFragment.kt
+++ b/app/src/main/java/com/anytypeio/anytype/ui/payments/PaymentsFragment.kt
@@ -19,6 +19,7 @@ import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
import com.anytypeio.anytype.di.common.componentManager
import com.anytypeio.anytype.screens.CodeScreen
import com.anytypeio.anytype.screens.MainPaymentsScreen
+import com.anytypeio.anytype.screens.PaymentWelcomeScreen
import com.anytypeio.anytype.screens.TierScreen
import com.anytypeio.anytype.ui.settings.typography
import com.anytypeio.anytype.viewmodel.PaymentsNavigation
@@ -62,6 +63,10 @@ class PaymentsFragment : BaseBottomSheetComposeFragment() {
when (command) {
PaymentsNavigation.Tier -> navController.navigate(PaymentsNavigation.Tier.route)
PaymentsNavigation.Code -> navController.navigate(PaymentsNavigation.Code.route)
+ PaymentsNavigation.Welcome -> {
+ navController.popBackStack(PaymentsNavigation.Main.route, false)
+ navController.navigate(PaymentsNavigation.Welcome.route)
+ }
PaymentsNavigation.Dismiss -> navController.popBackStack()
else -> {}
}
@@ -84,19 +89,24 @@ class PaymentsFragment : BaseBottomSheetComposeFragment() {
private fun NavigationGraph(navController: NavHostController) {
NavHost(navController = navController, startDestination = PaymentsNavigation.Main.route) {
composable(PaymentsNavigation.Main.route) {
- MainPaymentsScreen()
+ InitMainPaymentsScreen()
}
bottomSheet(PaymentsNavigation.Tier.route) {
- TierScreen()
+ InitTierScreen()
}
bottomSheet(PaymentsNavigation.Code.route) {
- CodeScreen()
+ InitCodeScreen()
+ }
+ bottomSheet(PaymentsNavigation.Welcome.route) {
+ InitWelcomeScreen()
}
}
}
@Composable
- private fun MainPaymentsScreen() {
+ private fun InitMainPaymentsScreen() {
+ skipCollapsed()
+ expand()
MainPaymentsScreen(
state = vm.viewState.collectAsStateWithLifecycle().value,
tierClicked = vm::onTierClicked
@@ -104,24 +114,32 @@ class PaymentsFragment : BaseBottomSheetComposeFragment() {
}
@Composable
- private fun TierScreen() {
+ private fun InitTierScreen() {
TierScreen(
- tier = vm.selectedTier.collectAsStateWithLifecycle().value,
+ state = vm.tierState.collectAsStateWithLifecycle().value,
onDismiss = vm::onDismissTier,
actionPay = vm::onPayButtonClicked
)
}
@Composable
- private fun CodeScreen() {
+ private fun InitCodeScreen() {
CodeScreen(
- state = vm.codeViewState.collectAsStateWithLifecycle().value,
+ state = vm.codeState.collectAsStateWithLifecycle().value,
actionResend = { },
actionCode = vm::onActionCode,
onDismiss = vm::onDismissCode
)
}
+ @Composable
+ private fun InitWelcomeScreen() {
+ PaymentWelcomeScreen(
+ state = vm.welcomeState.collectAsStateWithLifecycle().value,
+ onDismiss = vm::onDismissWelcome
+ )
+ }
+
override fun injectDependencies() {
componentManager().paymentsComponent.get().inject(this)
}
diff --git a/core-ui/src/main/res/values/colors.xml b/core-ui/src/main/res/values/colors.xml
index 91e83ef526..6719598ea8 100644
--- a/core-ui/src/main/res/values/colors.xml
+++ b/core-ui/src/main/res/values/colors.xml
@@ -223,4 +223,6 @@
@color/palette_system_amber_50
@color/shape_primary
+ #F6F6F6
+
diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml
index 539b56e182..d9fd05906f 100644
--- a/localization/src/main/res/values/strings.xml
+++ b/localization/src/main/res/values/strings.xml
@@ -1343,20 +1343,20 @@
- Let’s build\ntogether
+ Membership
Joining Anytype network means contributing to its story
- Co-create with us
- Stay closely connected with our team and community. Join calls with the team, influence Anytype\'s evolution, and have your say on features.
+ Co-create the Vision
+ As a valued member your voice matters! Engage in exclusive events, shape strategic choices, and influence our roadmap.
- Gain Benefits
- Our members have unique identity on Anytype Network, more storage, shared spaces and members per space for extensive collaboration.
+ Unlock Member Benefits
+ Members enjoy higher backup storage & sync limits, invitations for multiple guests to collaborate in shared spaces, and a unique identity on the Anytype Network.
- Support the Vision
- Your contribution supports our independent team and endorses our vision︎ of a user-driven, secure, and collaborative digital environment.
+ Support Digital Independence
+ Your contribution supports our team and endorses our vision of a user-owned, secure, and collaborative digital network.
Invest in Connectivity
- Our software is free by design, but we thrive on the network that connects us all. Support us, and you\'re investing in the very infrastructure that keeps us united.
+ Our network\'s value exceeds the sum of its parts. Your membership sustains the infrastructure for its growth which underpins this network.
Explorer
Dive into the network and enjoy the thrill of one-on-one collaboration
@@ -1375,24 +1375,25 @@
Terms and conditions
Would you like to use Anytype for business, education, etc.?
Please let us know here.
+ Current
-
- Pick your unique name
- This is your unique name on the Anytype network, confirming your Membership. It acts as your personal domain and cannot be changed.
- Myself
- .any
- Min 7 characters
- This name is already taken!
- This name is up for grabs!
+
+ Pick your unique name
+ This is your unique name on the Anytype network, confirming your Membership. It acts as your personal domain and cannot be changed.
+ Myself
+ .any
+ Min 7 characters
+ This name is already taken!
+ This name is up for grabs!
- Dive into the network and enjoy the thrill of one-on-one collaboration
+ Dive into the network and enjoy the thrill of one-on-one collaboration
- 1 GB of network space
- 10 one-to-one spaces
- Up to 10 shared spaces in read-only mode
- Unlock the magic of multi-party collaboration and enjoy top-notch support
+ Unlock the magic of multi-party collaboration and enjoy top-notch support
- Unique name (from 7 characters)
- 128 GB of network space
@@ -1400,7 +1401,7 @@
- Priority support
- Support our adventure and unlock exclusive access and perks
+ Support our adventure and unlock exclusive access and perks
- Unique name (from 5 characters)
- 256 GB of network space
@@ -1417,10 +1418,20 @@
I\'d like to get updates on products and enjoy free perks!
E-mail
+ Your current status:
+ Valid until
+ Paid by Card
+ Manage payment
+
Enter the code sent to your email
Resend
Resend in %1$n sec
+
+ Welcome to the network,\n%1$s
+ Big cheers for your curiosity!
+ Continue
+
\ No newline at end of file
diff --git a/payments/src/main/java/com/anytypeio/anytype/models/Tier.kt b/payments/src/main/java/com/anytypeio/anytype/models/Tier.kt
index 0395eebad0..0aa090e4a0 100644
--- a/payments/src/main/java/com/anytypeio/anytype/models/Tier.kt
+++ b/payments/src/main/java/com/anytypeio/anytype/models/Tier.kt
@@ -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()
}
\ No newline at end of file
diff --git a/payments/src/main/java/com/anytypeio/anytype/screens/EnterCode.kt b/payments/src/main/java/com/anytypeio/anytype/screens/EnterCode.kt
index 96331f099a..8b7a6db2ce 100644
--- a/payments/src/main/java/com/anytypeio/anytype/screens/EnterCode.kt
+++ b/payments/src/main/java/com/anytypeio/anytype/screens/EnterCode.kt
@@ -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() }
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 = { _ -> }
)
}
\ No newline at end of file
diff --git a/payments/src/main/java/com/anytypeio/anytype/screens/MainPaymensScreen.kt b/payments/src/main/java/com/anytypeio/anytype/screens/MainPaymensScreen.kt
index 12bc3e96ee..e42e19b14b 100644
--- a/payments/src/main/java/com/anytypeio/anytype/screens/MainPaymensScreen.kt
+++ b/payments/src/main/java/com/anytypeio/anytype/screens/MainPaymensScreen.kt
@@ -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, tierClicked: (Tier) -> Unit) {
+fun TiersList(tiers: List, 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, 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(
diff --git a/payments/src/main/java/com/anytypeio/anytype/screens/MembershipLevel.kt b/payments/src/main/java/com/anytypeio/anytype/screens/MembershipLevel.kt
index 35c5ad3fe2..48a596c895 100644
--- a/payments/src/main/java/com/anytypeio/anytype/screens/MembershipLevel.kt
+++ b/payments/src/main/java/com/anytypeio/anytype/screens/MembershipLevel.kt
@@ -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 = {}
)
diff --git a/payments/src/main/java/com/anytypeio/anytype/screens/PaymentWelcomeScreen.kt b/payments/src/main/java/com/anytypeio/anytype/screens/PaymentWelcomeScreen.kt
new file mode 100644
index 0000000000..26e0dfd590
--- /dev/null
+++ b/payments/src/main/java/com/anytypeio/anytype/screens/PaymentWelcomeScreen.kt
@@ -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")), {})
+}
\ No newline at end of file
diff --git a/payments/src/main/java/com/anytypeio/anytype/screens/TierView.kt b/payments/src/main/java/com/anytypeio/anytype/screens/TierView.kt
index 0874bbdfee..9a7403cd1f 100644
--- a/payments/src/main/java/com/anytypeio/anytype/screens/TierView.kt
+++ b/payments/src/main/java/com/anytypeio/anytype/screens/TierView.kt
@@ -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
)
}
diff --git a/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsMainState.kt b/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsMainState.kt
new file mode 100644
index 0000000000..944c073590
--- /dev/null
+++ b/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsMainState.kt
@@ -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) : PaymentsMainState()
+ data class PaymentSuccess(val tiers: List) : 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)
\ No newline at end of file
diff --git a/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsState.kt b/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsState.kt
deleted file mode 100644
index 95560f72a3..0000000000
--- a/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsState.kt
+++ /dev/null
@@ -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) : PaymentsState()
- data class PaymentSuccess(val tiers: List) : 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("")
-}
\ No newline at end of file
diff --git a/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsViewModel.kt b/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsViewModel.kt
index 32140ce244..63c8ea7360 100644
--- a/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsViewModel.kt
+++ b/payments/src/main/java/com/anytypeio/anytype/viewmodel/PaymentsViewModel.kt
@@ -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.Loading)
- val codeViewState = MutableStateFlow(PaymentsCodeState.Empty)
+ val viewState = MutableStateFlow(PaymentsMainState.Loading)
+ val codeState = MutableStateFlow(PaymentsCodeState.Hidden)
+ val tierState = MutableStateFlow(PaymentsTierState.Hidden)
+ val welcomeState = MutableStateFlow(PaymentsWelcomeState.Hidden)
+
val command = MutableStateFlow(null)
- val selectedTier = MutableStateFlow(null)
+ private val _tiers = mutableListOf()
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 {
+ 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")
+ )
+ }
}
\ No newline at end of file