1
0
Fork 0
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:
Konstantin Ivanov 2024-03-19 13:47:26 +01:00 committed by GitHub
parent 9104566899
commit 74ef0d58fd
Signed by: github
GPG key ID: B5690EEEBB952194
2 changed files with 478 additions and 0 deletions

View file

@ -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"
)
)
)
}

View file

@ -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 = {}
)
}