Skip to content

Instantly share code, notes, and snippets.

@BobbyESP
Created April 9, 2024 21:13
Show Gist options
  • Save BobbyESP/ba5d1a7380301c32028700ec74ea83a0 to your computer and use it in GitHub Desktop.
Save BobbyESP/ba5d1a7380301c32028700ec74ea83a0 to your computer and use it in GitHub Desktop.
Shared scope testing
SharedTransitionLayout {
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
LazyColumnScrollbar(
listState = lazyListState,
thumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
thumbSelectedColor = MaterialTheme.colorScheme.primary,
selectionActionable = ScrollbarSelectionActionable.WhenVisible,
) {
var cardPdf by remember {
mutableStateOf<SavedPdf?>(null)
}
AnimatedContent(
modifier = Modifier, targetState = cardPdf, label = "") { chosenPdf ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
state = lazyListState,
) {
items(
count = pdfs.size,
key = { index -> pdfs[index].savedTimestamp },
contentType = { index -> pdfs[index].savedTimestamp.toString() }) { index ->
val pdf = pdfs[index]
SavedPdfListItemTransitionsWrapper(
pdf = pdf,
onClick = { onOpenPdf(pdf) },
onLongPressed = {
cardPdf = pdf
},
)
}
}
if(chosenPdf == null) return@AnimatedContent
SavedPdfCardTransitionsWrapper(
modifier = Modifier,
pdf = chosenPdf,
onShareRequest = onSharePdf,
onOpenPdf = { cardPdf?.let { pdf -> onOpenPdf(pdf) } },
onDeleteRequest = {
pdfItemToRemove = it
showDeleteDialog = !showDeleteDialog
}
) {
cardPdf = null
}
}
}
}
}
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile
import androidx.compose.material.icons.rounded.AccountTree
import androidx.compose.material.icons.rounded.CalendarMonth
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.FileOpen
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.bobbyesp.docucraft.R
import com.bobbyesp.docucraft.domain.model.SavedPdf
import com.bobbyesp.docucraft.presentation.theme.DocucraftTheme
import com.bobbyesp.ui.components.layouts.lazygrid.GridMenuItem
import com.bobbyesp.ui.motion.MotionConstants.DURATION_ENTER
import com.bobbyesp.ui.motion.MotionConstants.DURATION_EXIT_SHORT
import com.bobbyesp.ui.motion.MotionConstants.EmphasizedAccelerateEasing
import com.bobbyesp.ui.motion.MotionConstants.EmphasizedDecelerateEasing
import com.bobbyesp.ui.motion.MotionConstants.boundsTransformation
import com.bobbyesp.utilities.Time
import com.bobbyesp.utilities.parseFileSize
@Composable
fun SavedPdfFileCard(
modifier: Modifier = Modifier,
contentModifier: Modifier = Modifier,
pdf: SavedPdf,
onOpenPdf: (SavedPdf) -> Unit = {},
onShareRequest: (SavedPdf) -> Unit = {},
onDeleteRequest: (SavedPdf) -> Unit = {}
) {
val fileSize: String? by remember {
mutableStateOf(pdf.fileSizeBytes?.let { parseFileSize(it) })
}
val creationTime by remember {
mutableStateOf(Time.Localized.getFormattedDate(pdf.savedTimestamp))
}
Surface(
modifier = modifier.clip(MaterialTheme.shapes.small),
color = MaterialTheme.colorScheme.surface
) {
Column(
modifier = contentModifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.Start
) {
Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
modifier = Modifier
.size(64.dp)
.clip(MaterialTheme.shapes.extraSmall)
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(6.dp),
imageVector = Icons.AutoMirrored.Rounded.InsertDriveFile,
contentDescription = stringResource(id = R.string.file_icon),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
Column(
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp)
) {
Text(
text = pdf.title ?: pdf.fileName,
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.bodyLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
modifier = Modifier,
text = pdf.description ?: stringResource(id = R.string.no_description),
fontWeight = FontWeight.Normal,
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
MetadataInfo(
modifier = Modifier, icon = Icons.Rounded.CalendarMonth, text = stringResource(
id = R.string.created_at
) + " " + creationTime
)
// pdf.path?.let {
// MetadataInfo(
// modifier = Modifier, icon = Icons.Rounded.Place, text = it.toString()
// )
// }
MetadataInfo(
modifier = Modifier,
icon = Icons.Rounded.AccountTree,
text = pdf.pageCount.toString() + " " + stringResource(id = R.string.pages)
)
fileSize?.let {
MetadataInfo(
modifier = Modifier,
icon = Icons.AutoMirrored.Rounded.InsertDriveFile,
text = stringResource(id = R.string.size) + ": " + it
)
}
}
HorizontalDivider()
LazyVerticalGrid(
modifier = Modifier.fillMaxWidth(), columns = GridCells.Adaptive(minSize = 100.dp)
) {
GridMenuItem(icon = { Icons.Rounded.FileOpen },
title = { stringResource(id = R.string.open_file) }) {
onOpenPdf(pdf)
}
GridMenuItem(icon = { Icons.Rounded.Share },
title = { stringResource(id = R.string.share) }) {
onShareRequest(pdf)
}
GridMenuItem(icon = { Icons.Rounded.Delete },
title = { stringResource(id = R.string.delete) }) {
onDeleteRequest(pdf)
}
}
}
}
}
context(AnimatedContentScope, SharedTransitionScope)
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SavedPdfCardTransitionsWrapper(
modifier: Modifier = Modifier,
pdf: SavedPdf,
onOpenPdf: (SavedPdf) -> Unit = {},
onShareRequest: (SavedPdf) -> Unit = {},
onDeleteRequest: (SavedPdf) -> Unit = {},
onDismissRequest: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = onDismissRequest
),
contentAlignment = Alignment.Center
) {
SavedPdfFileCard(
modifier = Modifier.sharedBounds(
boundsTransform = boundsTransformation,
enter = fadeIn(
tween(
durationMillis = DURATION_ENTER,
delayMillis = DURATION_EXIT_SHORT,
easing = EmphasizedDecelerateEasing
)
),
exit = fadeOut(
tween(
durationMillis = DURATION_EXIT_SHORT,
easing = EmphasizedAccelerateEasing
)
),
sharedContentState = rememberSharedContentState(key = "${pdf.savedTimestamp}_bounds"),
animatedVisibilityScope = this@AnimatedContentScope,
placeHolderSize = SharedTransitionScope.PlaceHolderSize.animatedSize,
),
contentModifier = modifier,
pdf = pdf,
onShareRequest = onShareRequest,
onOpenPdf = onOpenPdf,
onDeleteRequest = onDeleteRequest
)
}
}
@Composable
private fun MetadataInfo(modifier: Modifier = Modifier, icon: ImageVector, text: String) {
Row(
modifier = modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
Icon(
modifier = Modifier.size(24.dp),
imageVector = icon,
contentDescription = stringResource(
id = R.string.metadata_icon
),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.InsertDriveFile
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.bobbyesp.docucraft.R
import com.bobbyesp.docucraft.domain.model.SavedPdf
import com.bobbyesp.docucraft.presentation.theme.DocucraftTheme
import com.bobbyesp.ui.motion.MotionConstants.DURATION_ENTER
import com.bobbyesp.ui.motion.MotionConstants.DURATION_EXIT_SHORT
import com.bobbyesp.ui.motion.MotionConstants.EmphasizedAccelerateEasing
import com.bobbyesp.ui.motion.MotionConstants.EmphasizedDecelerateEasing
import com.bobbyesp.ui.motion.MotionConstants.boundsTransformation
import com.bobbyesp.utilities.parseFileSize
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun SavedPdfFileListItem(
modifier: Modifier = Modifier,
contentModifier: Modifier = Modifier,
pdf: SavedPdf,
onClick: () -> Unit,
onLongPressed: () -> Unit
) {
val fileSize: String? by remember {
mutableStateOf(pdf.fileSizeBytes?.let { parseFileSize(it) })
}
Surface(
modifier = modifier.combinedClickable(
onClick = onClick,
onLongClick = onLongPressed
)
) {
Row(
modifier = contentModifier.padding(horizontal = 12.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = Modifier
.size(32.dp)
.clip(MaterialTheme.shapes.extraSmall)
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(6.dp),
imageVector = Icons.AutoMirrored.Rounded.InsertDriveFile,
contentDescription = stringResource(id = R.string.file_icon),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
Column(
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp)
) {
Text(
modifier = Modifier,
text = pdf.title ?: pdf.fileName,
fontWeight = FontWeight.SemiBold,
style = MaterialTheme.typography.bodyLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
modifier = Modifier,
text = pdf.description ?: stringResource(id = R.string.no_description),
fontWeight = FontWeight.Normal,
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Column(
modifier = Modifier,
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.End
) {
fileSize?.let {
Text(
text = stringResource(id = R.string.size) + " " + it,
style = MaterialTheme.typography.bodySmall.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
),
)
}
}
}
}
}
context(AnimatedContentScope, SharedTransitionScope)
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun SavedPdfListItemTransitionsWrapper(
modifier: Modifier = Modifier,
pdf: SavedPdf,
onClick: () -> Unit,
onLongPressed: () -> Unit,
) {
var height by remember { mutableStateOf<Dp?>(null) }
val density = LocalDensity.current
Box(
modifier = modifier
.onSizeChanged {
height = density.run { it.height.toDp() }
}
) {
height?.let {
Spacer(modifier = Modifier.height(it))
}
SavedPdfFileListItem(
modifier = Modifier.sharedBounds(
boundsTransform = boundsTransformation,
enter = fadeIn(
tween(
durationMillis = DURATION_ENTER,
delayMillis = DURATION_EXIT_SHORT,
easing = EmphasizedDecelerateEasing
)
),
exit = fadeOut(
tween(
durationMillis = DURATION_EXIT_SHORT,
easing = EmphasizedAccelerateEasing
)
),
sharedContentState = rememberSharedContentState(key = "${pdf.savedTimestamp}_bounds"),
animatedVisibilityScope = this@AnimatedContentScope,
placeHolderSize = SharedTransitionScope.PlaceHolderSize.animatedSize,
),
pdf = pdf, onClick = onClick, onLongPressed = onLongPressed
)
}
}
@Preview
@Preview(uiMode = UI_MODE_NIGHT_YES)
@Composable
private fun CardPrev() {
DocucraftTheme {
SavedPdfFileListItem(pdf = SavedPdf.emptyPdf(), onClick = { /*TODO*/ }) {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment