mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-1776 Gallery experience | Design | Main screens (#1015)
This commit is contained in:
parent
9104566899
commit
74ef0d58fd
2 changed files with 478 additions and 0 deletions
|
@ -1,6 +1,60 @@
|
|||
package com.anytypeio.anytype.gallery_experience.screens
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
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.wrapContentWidth
|
||||
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.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import com.anytypeio.anytype.core_models.ManifestInfo
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonPrimary
|
||||
import com.anytypeio.anytype.core_ui.views.ButtonSize
|
||||
import com.anytypeio.anytype.core_ui.views.Caption1Medium
|
||||
import com.anytypeio.anytype.core_ui.views.Caption1Regular
|
||||
import com.anytypeio.anytype.core_ui.views.HeadlineTitle
|
||||
import com.anytypeio.anytype.core_ui.views.UXBody
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationState
|
||||
|
||||
@Composable
|
||||
|
@ -8,4 +62,239 @@ fun GalleryInstallationScreen(
|
|||
state: GalleryInstallationState,
|
||||
onInstallClicked: () -> Unit
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.nestedScroll(rememberNestedScrollInteropConnection())
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.background(
|
||||
color = colorResource(id = R.color.background_secondary),
|
||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(bottom = 20.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
when (state) {
|
||||
GalleryInstallationState.Hidden -> {}
|
||||
GalleryInstallationState.Loading -> {
|
||||
LoadingScreen()
|
||||
}
|
||||
is GalleryInstallationState.Success -> {
|
||||
SuccessScreen(state, onInstallClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingScreen() {
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "")
|
||||
val translateAnim by infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 1000f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(
|
||||
durationMillis = 200,
|
||||
easing = LinearEasing
|
||||
),
|
||||
repeatMode = RepeatMode.Restart
|
||||
), label = ""
|
||||
)
|
||||
val brush = Brush.linearGradient(
|
||||
colors = listOf(
|
||||
Color(0xFFF5F5F5),
|
||||
Color(0xFFEBEBEB),
|
||||
Color(0xFFF5F5F5)
|
||||
),
|
||||
start = Offset(10f, 10f),
|
||||
end = Offset(translateAnim, translateAnim)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(400.dp)
|
||||
.padding(horizontal = 20.dp)
|
||||
.background(shape = RoundedCornerShape(12.dp), brush = brush)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(32.dp)
|
||||
.padding(start = 20.dp, end = 52.dp)
|
||||
.background(brush = brush, shape = RoundedCornerShape(4.dp))
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(12.dp)
|
||||
.padding(start = 20.dp, end = 20.dp)
|
||||
.background(brush = brush, shape = RoundedCornerShape(4.dp))
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(12.dp)
|
||||
.padding(start = 20.dp, end = 95.dp)
|
||||
.background(brush = brush, shape = RoundedCornerShape(4.dp))
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(12.dp)
|
||||
.padding(start = 20.dp, end = 35.dp)
|
||||
.background(brush = brush, shape = RoundedCornerShape(4.dp))
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun SuccessScreen(
|
||||
state: GalleryInstallationState.Success,
|
||||
onInstallClicked: () -> Unit
|
||||
) {
|
||||
val dotCurrentColor = colorResource(id = R.color.glyph_active)
|
||||
val dotColor = colorResource(id = R.color.glyph_inactive)
|
||||
val pagerState = rememberPagerState {
|
||||
state.info.screenshots.size
|
||||
}
|
||||
Spacer(modifier = Modifier.height(71.dp))
|
||||
HorizontalPager(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(220.dp)
|
||||
.padding(horizontal = 20.dp),
|
||||
state = pagerState
|
||||
) { index ->
|
||||
val screenshotUrl = state.info.screenshots[index]
|
||||
val imageModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(shape = RoundedCornerShape(8.dp))
|
||||
AsyncImage(
|
||||
modifier = imageModifier,
|
||||
model = screenshotUrl,
|
||||
contentDescription = "Gallery experience screenshot",
|
||||
placeholder = ColorPainter(colorResource(id = R.color.background_secondary)),
|
||||
error = ColorPainter(colorResource(id = R.color.background_secondary)),
|
||||
onError = { _ -> imageModifier.shadow(4.dp) },
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(60.dp))
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(24.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
repeat(state.info.screenshots.size) { iteration ->
|
||||
val color =
|
||||
if (pagerState.currentPage == iteration) dotCurrentColor else dotColor
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 9.dp)
|
||||
.background(color, CircleShape)
|
||||
.size(8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp),
|
||||
text = state.info.title,
|
||||
style = HeadlineTitle,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp),
|
||||
text = state.info.description,
|
||||
style = UXBody,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp),
|
||||
text = "${stringResource(id = R.string.gallery_experience_made)} @${state.info.author}",
|
||||
style = Caption1Regular,
|
||||
color = colorResource(id = R.color.text_secondary)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 20.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
state.info.categories.forEach { category ->
|
||||
CategoryItem(item = category)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(39.dp))
|
||||
ButtonPrimary(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp),
|
||||
onClick = { onInstallClicked() },
|
||||
size = ButtonSize.Large,
|
||||
text = stringResource(id = R.string.gallery_experience_install)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CategoryItem(item: String) {
|
||||
Text(
|
||||
text = item,
|
||||
color = colorResource(id = R.color.text_secondary),
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.background(
|
||||
color = colorResource(id = R.color.shape_tertiary),
|
||||
shape = RoundedCornerShape(size = 4.dp)
|
||||
)
|
||||
.padding(start = 6.dp, end = 6.dp, top = 1.dp, bottom = 1.dp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = Caption1Medium
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun GalleryInstallationScreenPreview() {
|
||||
GalleryInstallationScreen(
|
||||
onInstallClicked = {},
|
||||
state = GalleryInstallationState.Success(
|
||||
info = ManifestInfo(
|
||||
schema = "placerat",
|
||||
id = "vix",
|
||||
name = "Agnes Lucas",
|
||||
author = "constituto",
|
||||
license = "accommodare",
|
||||
title = "reprimique",
|
||||
description = "pretium",
|
||||
screenshots = listOf("1", "2", "3", "4"),
|
||||
downloadLink = "lobortis",
|
||||
fileSize = 1213,
|
||||
categories = listOf("tag1", "tag2", "tag3312212112", "tag421312312", "tag5", "tag6"),
|
||||
language = "nisi"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -1,9 +1,46 @@
|
|||
package com.anytypeio.anytype.gallery_experience.screens
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
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.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.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
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.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.toColorInt
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import com.anytypeio.anytype.core_models.ObjectWrapper
|
||||
import com.anytypeio.anytype.gallery_experience.models.GalleryInstallationSpacesState
|
||||
import com.anytypeio.anytype.core_ui.R
|
||||
import com.anytypeio.anytype.core_ui.foundation.Dragger
|
||||
import com.anytypeio.anytype.core_ui.foundation.noRippleThrottledClickable
|
||||
import com.anytypeio.anytype.core_ui.views.Title3
|
||||
import com.anytypeio.anytype.gallery_experience.models.GallerySpaceView
|
||||
import com.anytypeio.anytype.presentation.spaces.SpaceIconView
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
@ -13,5 +50,157 @@ fun GalleryInstallationSpacesScreen(
|
|||
onSpaceClick: (GallerySpaceView) -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||
ModalBottomSheet(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp, end = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
sheetState = sheetState,
|
||||
onDismissRequest = onDismiss,
|
||||
containerColor = colorResource(id = R.color.background_secondary),
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 8.dp, end = 8.dp, bottom = 32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Dragger()
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
if (state.isNewButtonVisible) {
|
||||
NewSpaceItem(onNewSpaceClick = onNewSpaceClick)
|
||||
}
|
||||
state.spaces.forEach { space ->
|
||||
SpaceItem(space = space, onSpaceClick = onSpaceClick)
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
dragHandle = null
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NewSpaceItem(onNewSpaceClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(64.dp)
|
||||
.padding(horizontal = 20.dp)
|
||||
.noRippleThrottledClickable { onNewSpaceClick() },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.background(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = colorResource(id = R.color.background_highlighted).copy(alpha = 0.04f)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = colorResource(id = R.color.shape_secondary),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
),
|
||||
painter = painterResource(id = R.drawable.ic_default_plus),
|
||||
contentDescription = "Install to new space",
|
||||
contentScale = ContentScale.Inside
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp, end = 20.dp),
|
||||
text = stringResource(id = R.string.gallery_experience_install_new),
|
||||
style = Title3,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpaceItem(space: GallerySpaceView, onSpaceClick: (GallerySpaceView) -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(64.dp)
|
||||
.padding(horizontal = 20.dp)
|
||||
.noRippleThrottledClickable { onSpaceClick(space) },
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
SpaceIcon(
|
||||
icon = space.icon,
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 12.dp, end = 20.dp),
|
||||
text = space.obj.name.orEmpty(),
|
||||
style = Title3,
|
||||
color = colorResource(id = R.color.text_primary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpaceIcon(
|
||||
icon: SpaceIconView,
|
||||
modifier: Modifier
|
||||
) {
|
||||
when (icon) {
|
||||
is SpaceIconView.Image -> {
|
||||
Image(
|
||||
painter = rememberAsyncImagePainter(
|
||||
model = icon.url,
|
||||
error = painterResource(id = R.drawable.ic_home_widget_space)
|
||||
),
|
||||
contentDescription = "Custom image space icon",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
)
|
||||
}
|
||||
|
||||
is SpaceIconView.Gradient -> {
|
||||
val gradient = Brush.radialGradient(
|
||||
colors = listOf(
|
||||
Color(icon.from.toColorInt()),
|
||||
Color(icon.to.toColorInt())
|
||||
)
|
||||
)
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(CircleShape)
|
||||
.background(gradient)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Draw nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun GallerySpacesScreenPreview() {
|
||||
GalleryInstallationSpacesScreen(
|
||||
state = GalleryInstallationSpacesState(
|
||||
listOf(
|
||||
GallerySpaceView(
|
||||
obj = ObjectWrapper.SpaceView(map = mapOf("name" to "Space 1")),
|
||||
icon = SpaceIconView.Placeholder
|
||||
)
|
||||
),
|
||||
isNewButtonVisible = true
|
||||
),
|
||||
onNewSpaceClick = {},
|
||||
onSpaceClick = {},
|
||||
onDismiss = {}
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue