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 | UI | Basic screens (#982)

This commit is contained in:
Konstantin Ivanov 2024-03-06 14:23:39 +01:00 committed by GitHub
parent 818f937259
commit 0082e6daa0
Signed by: github
GPG key ID: B5690EEEBB952194
16 changed files with 1271 additions and 11 deletions

View file

@ -0,0 +1,134 @@
package com.anytypeio.anytype.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
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.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
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.unit.dp
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.views.BodyBold
import com.anytypeio.anytype.core_ui.views.Relations2
@Composable
fun InfoCard(
image: Int,
gradient: Brush,
title: String,
subtitle: String,
) {
val configuration = LocalConfiguration.current
Column(
modifier = Modifier
.height(284.dp)
.background(color = colorResource(id = R.color.shape_tertiary)),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(136.dp)
.background(gradient)
.verticalScroll(rememberScrollState())
) {
Image(
modifier = Modifier
.fillMaxWidth()
.padding(top = 32.dp),
painter = painterResource(id = image),
contentDescription = "Main payments image"
)
}
Spacer(modifier = Modifier.height(24.dp))
Text(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
text = title,
color = colorResource(id = R.color.text_primary),
style = BodyBold,
textAlign = TextAlign.Center
)
Text(
modifier = Modifier
.padding(start = 32.dp, end = 32.dp, top = 6.dp)
.verticalScroll(rememberScrollState()),
text = subtitle,
color = colorResource(id = R.color.text_primary),
style = Relations2,
textAlign = TextAlign.Center
)
}
}
@Composable
fun infoCardsState() = listOf(
InfoCardState(
image = R.drawable.payments_card_0,
title = stringResource(id = R.string.payments_card_text_1),
subtitle = stringResource(id = R.string.payments_card_description_1),
gradient = Brush.verticalGradient(
colors = listOf(
Color(0xFFCFF6CF),
Color.Transparent
)
)
),
InfoCardState(
image = R.drawable.payments_card_1,
title = stringResource(id = R.string.payments_card_text_2),
subtitle = stringResource(id = R.string.payments_card_description_2),
gradient = Brush.verticalGradient(
colors = listOf(
Color(0xFFFEF2C6),
Color.Transparent
)
)
),
InfoCardState(
image = R.drawable.payments_card_2,
title = stringResource(id = R.string.payments_card_text_3),
subtitle = stringResource(id = R.string.payments_card_description_3),
gradient = Brush.verticalGradient(
colors = listOf(
Color(0xFFFFEBEB),
Color.Transparent
)
)
),
InfoCardState(
image = R.drawable.payments_card_3,
title = stringResource(id = R.string.payments_card_text_4),
subtitle = stringResource(id = R.string.payments_card_description_4),
gradient = Brush.verticalGradient(
colors = listOf(
Color(0xFFEBEDFE),
Color.Transparent
)
)
)
)
data class InfoCardState(
val image: Int,
val title: String,
val subtitle: String,
val gradient: Brush
)

View file

@ -1,13 +1,29 @@
package com.anytypeio.anytype.screens
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
@ -17,12 +33,28 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import com.anytypeio.anytype.core_ui.R
import com.anytypeio.anytype.core_ui.foundation.Divider
import com.anytypeio.anytype.core_ui.foundation.Dragger
import com.anytypeio.anytype.core_ui.views.Title1
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
import com.anytypeio.anytype.core_ui.views.BodyRegular
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.viewmodel.PaymentsState
import com.anytypeio.anytype.viewmodel.TierState
@Composable
fun MainPaymentsScreen(state: PaymentsState) {
@ -40,14 +72,28 @@ fun MainPaymentsScreen(state: PaymentsState) {
modifier = Modifier
.fillMaxSize()
.padding(bottom = 20.dp)
.verticalScroll(rememberScrollState())
) {
Header(state = state)
if (state is PaymentsState.Success) {
Header(state = state)
Spacer(modifier = Modifier.height(32.dp))
InfoCards()
Tiers(state = state)
Spacer(modifier = Modifier.height(32.dp))
LinkButton(text = stringResource(id = R.string.payments_member_link), action = {})
Divider()
LinkButton(text = stringResource(id = R.string.payments_privacy_link), action = {})
Divider()
LinkButton(text = stringResource(id = R.string.payments_terms_link), action = {})
Spacer(modifier = Modifier.height(32.dp))
BottomText()
}
}
}
}
@Composable
private fun Header(state: PaymentsState) {
private fun Header(state: PaymentsState.Success) {
// Dragger at the top, centered
Box(
@ -63,14 +109,166 @@ private fun Header(state: PaymentsState) {
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.verticalScroll(rememberScrollState())
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "Let's build together",
modifier = Modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp, top = 37.dp),
text = stringResource(id = R.string.payments_header),
color = colorResource(id = R.color.text_primary),
style = Title1,
style = headerTextStyle,
textAlign = TextAlign.Center
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(start = 60.dp, end = 60.dp, top = 7.dp),
text = stringResource(id = R.string.payments_subheader),
color = colorResource(id = R.color.text_primary),
style = Relations2,
textAlign = TextAlign.Center
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Tiers(state: PaymentsState.Success) {
val itemsScroll = rememberLazyListState(initialFirstVisibleItemIndex = 1)
LazyRow(
state = itemsScroll,
modifier = Modifier
.fillMaxWidth()
.padding(top = 32.dp),
horizontalArrangement = Arrangement.spacedBy(20.dp),
contentPadding = PaddingValues(start = 20.dp, end = 20.dp),
flingBehavior = rememberSnapFlingBehavior(lazyListState = itemsScroll)
) {
itemsIndexed(state.tiers) { index, tier ->
TierByType(tier = tier)
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun InfoCards() {
val cards = infoCardsState()
val pagerState = rememberPagerState {
cards.size
}
val dotCurrentColor = colorResource(id = R.color.glyph_button)
val dotColor = colorResource(id = R.color.glyph_inactive)
Box(modifier = Modifier) {
HorizontalPager(state = pagerState) { index ->
val card = cards[index]
InfoCard(
gradient = card.gradient,
title = card.title,
subtitle = card.subtitle,
image = card.image
)
}
Row(
Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(bottom = 10.dp),
horizontalArrangement = Arrangement.Center
) {
repeat(cards.size) { iteration ->
val color =
if (pagerState.currentPage == iteration) dotCurrentColor else dotColor
Box(
modifier = Modifier
.padding(horizontal = 5.dp)
.background(color, CircleShape)
.size(6.dp)
)
}
}
}
}
@Composable
fun LinkButton(text: String, action: () -> Unit) {
Box(
modifier = Modifier
.height(52.dp)
.fillMaxWidth()
.padding(horizontal = 20.dp)
.noRippleThrottledClickable { action.invoke() }
) {
Text(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterStart),
text = text,
style = BodyRegular,
color = colorResource(id = R.color.text_primary)
)
Image(
modifier = Modifier
.wrapContentSize()
.align(Alignment.CenterEnd),
painter = painterResource(id = R.drawable.ic_web_link),
contentDescription = "web link icon"
)
}
}
@Composable
fun BottomText() {
val start = stringResource(id = R.string.payments_let_us_link_start)
val end = stringResource(id = R.string.payments_let_us_link_end)
val buildString = buildAnnotatedString {
append(start)
append(" ")
append(end)
pushStringAnnotation(
tag = "link", annotation = "www.anytype.io"
)
addStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
start = start.length + 1,
end = start.length + 1 + end.length
)
pop()
}
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.wrapContentHeight(),
text = buildString,
style = Caption1Regular,
color = colorResource(id = R.color.text_primary)
)
}
@Preview
@Composable
fun MainPaymentsScreenPreview() {
val tiers = listOf(
TierState.Explorer("999", isCurrent = true),
TierState.Builder("999", isCurrent = false),
TierState.CoCreator("999", isCurrent = false),
TierState.Custom("999", isCurrent = false)
)
MainPaymentsScreen(PaymentsState.Success(tiers))
}
val headerTextStyle = TextStyle(
fontFamily = fontRiccioneRegular,
fontWeight = FontWeight.W400,
fontSize = 48.sp,
lineHeight = 48.sp,
letterSpacing = (-0.010833).em
)

View file

@ -0,0 +1,200 @@
package com.anytypeio.anytype.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
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.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import com.anytypeio.anytype.core_ui.R
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.fontInterSemibold
import com.anytypeio.anytype.viewmodel.TierState
@Composable
private fun Tier(
title: String,
subTitle: String,
price: String,
colorGradient: Color,
radialGradient: Color,
icon: Int,
buttonText: String,
onClick: () -> Unit
) {
val brush = Brush.verticalGradient(
listOf(
colorGradient,
Color.Transparent
)
)
Column(
modifier = Modifier
.width(192.dp)
.wrapContentHeight()
.background(
color = colorResource(id = R.color.shape_tertiary),
shape = RoundedCornerShape(16.dp)
)
.noRippleThrottledClickable { onClick() }
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(80.dp)
.background(brush = brush, shape = RoundedCornerShape(16.dp)),
contentAlignment = androidx.compose.ui.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 = { /*TODO*/ },
size = ButtonSize.Small
)
Spacer(modifier = Modifier.height(10.dp))
}
}
@Composable
fun PriceOrOption() {
Text(
modifier = Modifier.padding(start = 16.dp),
text = "9.99",
style = titleTextStyle,
color = colorResource(id = R.color.text_primary)
)
}
@Composable
fun TierByType(tier: TierState) {
when (tier) {
is TierState.Builder -> {
Tier(
title = stringResource(id = R.string.payments_tier_builder),
subTitle = stringResource(id = R.string.payments_tier_builder_description),
price = tier.price,
colorGradient = Color(0xFFE4E7FF),
radialGradient = Color(0xFFA5AEFF),
icon = R.drawable.logo_builder,
buttonText = stringResource(id = R.string.payments_button_learn),
onClick = { /*TODO*/ }
)
}
is TierState.CoCreator -> {
Tier(
title = stringResource(id = R.string.payments_tier_cocreator),
subTitle = stringResource(id = R.string.payments_tier_cocreator_description),
price = tier.price,
colorGradient = Color(0xFFFBEAEA),
radialGradient = Color(0xFFF05F5F),
icon = R.drawable.logo_co_creator,
buttonText = stringResource(id = R.string.payments_button_learn),
onClick = { /*TODO*/ }
)
}
is TierState.Custom -> {
Tier(
title = stringResource(id = R.string.payments_tier_custom),
subTitle = stringResource(id = R.string.payments_tier_custom_description),
price = tier.price,
colorGradient = Color(0xFFFBEAFF),
radialGradient = Color(0xFFFE86DE3),
icon = R.drawable.logo_custom,
buttonText = stringResource(id = R.string.payments_button_learn),
onClick = { /*TODO*/ }
)
}
is TierState.Explorer -> {
Tier(
title = stringResource(id = R.string.payments_tier_explorer),
subTitle = stringResource(id = R.string.payments_tier_explorer_description),
price = tier.price,
colorGradient = Color(0xFFCFFAFF),
radialGradient = Color(0xFF24BFD4),
icon = R.drawable.logo_explorer,
buttonText = stringResource(id = R.string.payments_button_learn),
onClick = { /*TODO*/ }
)
}
}
}
@Preview
@Composable
fun TierPreview() {
Tier(
title = "Explorer",
subTitle = "Dive into the network and enjoy the thrill of one-on-one collaboration",
price = "9.99",
buttonText = "Subscribe",
onClick = {},
icon = R.drawable.logo_co_creator,
colorGradient = Color(0xFFCFF6CF),
radialGradient = Color(0xFF24BFD4)
)
}
val titleTextStyle = TextStyle(
fontFamily = fontInterSemibold,
fontWeight = FontWeight.W600,
fontSize = 17.sp,
lineHeight = 24.sp,
letterSpacing = (-0.024).em
)

View file

@ -2,6 +2,29 @@ package com.anytypeio.anytype.viewmodel
sealed class PaymentsState {
object Loading : PaymentsState()
object Error : PaymentsState()
object Success : PaymentsState()
data class Success(val tiers: List<TierState>) : PaymentsState()
}
sealed class TierState {
abstract val isCurrent: Boolean
data class Explorer(
val price: String,
override val isCurrent: Boolean
) : TierState()
data class Builder(
val price: String,
override val isCurrent: Boolean
) : TierState()
data class CoCreator(
val price: String,
override val isCurrent: Boolean
) : TierState()
data class Custom(
val price: String,
override val isCurrent: Boolean
) : TierState()
}

View file

@ -13,6 +13,14 @@ class PaymentsViewModel(
init {
Timber.d("PaymentsViewModel created")
viewState.value = PaymentsState.Success(
listOf(
TierState.Explorer("Free", true),
TierState.Builder("$9.99/mo", false),
TierState.CoCreator("$19.99/mo", false),
TierState.Custom("$29.99/mo", false)
)
)
}
interface PaymentsNavigation {