Skip to content

Commit

Permalink
split medium screen into section to improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
DatL4g committed Apr 25, 2024
1 parent 7743fee commit 9ea4d3f
Show file tree
Hide file tree
Showing 8 changed files with 680 additions and 413 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package dev.datlag.aniflow.ui.navigation.screen.medium.component

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
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.unit.dp
import dev.datlag.aniflow.SharedRes
import dev.datlag.aniflow.anilist.model.Character
import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.StateFlow

@Composable
fun CharacterSection(
characterFlow: StateFlow<Collection<Character>>,
modifier: Modifier = Modifier,
onClick: (Character) -> Unit
) {
val characters by characterFlow.collectAsStateWithLifecycle()

if (characters.isNotEmpty()) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
modifier = Modifier.padding(top = 16.dp).padding(horizontal = 16.dp),
text = stringResource(SharedRes.strings.characters),
style = MaterialTheme.typography.headlineSmall
)
LazyRow(
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally)
) {
items(characters.toList()) { char ->
CharacterCard(
char = char,
modifier = Modifier.width(96.dp).height(200.dp),
onClick = onClick
)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package dev.datlag.aniflow.ui.navigation.screen.medium.component

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.fillMaxWidth
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import coil3.compose.rememberAsyncImagePainter
import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import dev.datlag.aniflow.LocalHaze
import dev.datlag.aniflow.anilist.model.Medium
import dev.datlag.aniflow.common.notPreferred
import dev.datlag.aniflow.common.preferred
import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.StateFlow

@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
@Composable
fun CollapsingToolbar(
state: TopAppBarState,
scrollBehavior: TopAppBarScrollBehavior,
bannerImageFlow: StateFlow<String?>,
coverImage: Medium.CoverImage,
titleFlow: StateFlow<Medium.Title>,
onBack: () -> Unit
) {
Box(
modifier = Modifier.fillMaxWidth()
) {
val bannerImage by bannerImageFlow.collectAsStateWithLifecycle()
val isCollapsed by remember(state) {
derivedStateOf { state.collapsedFraction >= 0.99F }
}

AsyncImage(
modifier = Modifier
.fillMaxWidth()
.matchParentSize(),
model = bannerImage,
contentScale = ContentScale.Crop,
contentDescription = null,
error = rememberAsyncImagePainter(
model = coverImage.extraLarge,
contentScale = ContentScale.Crop,
error = rememberAsyncImagePainter(
model = coverImage.large,
contentScale = ContentScale.Crop
)
),
alpha = 1F - state.collapsedFraction
)
LargeTopAppBar(
navigationIcon = {
IconButton(
modifier = if (isCollapsed) {
Modifier
} else {
Modifier.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.75F), CircleShape)
},
onClick = {
onBack()
}
) {
Icon(
imageVector = Icons.Default.ArrowBackIosNew,
contentDescription = null
)
}
},
title = {
val title by titleFlow.collectAsStateWithLifecycle()

Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically)
) {
Text(
text = title.preferred(),
softWrap = true,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = if (!isCollapsed) {
LocalTextStyle.current.copy(
shadow = Shadow(
color = MaterialTheme.colorScheme.surface,
offset = Offset(4F, 4F),
blurRadius = 8F
)
)
} else {
LocalTextStyle.current
}
)
title.notPreferred()?.let {
Text(
text = it,
softWrap = true,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = if (!isCollapsed) {
MaterialTheme.typography.labelMedium.copy(
shadow = Shadow(
color = MaterialTheme.colorScheme.surface,
offset = Offset(4F, 4F),
blurRadius = 8F
)
)
} else {
MaterialTheme.typography.labelMedium
}
)
}
}
},
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.largeTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
),
modifier = Modifier.hazeChild(
state = LocalHaze.current,
style = HazeMaterials.thin(
containerColor = MaterialTheme.colorScheme.surface
)
).fillMaxWidth()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package dev.datlag.aniflow.ui.navigation.screen.medium.component

import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.OndemandVideo
import androidx.compose.material.icons.filled.RssFeed
import androidx.compose.material.icons.filled.Timelapse
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import coil3.compose.rememberAsyncImagePainter
import dev.datlag.aniflow.anilist.model.Medium
import dev.datlag.aniflow.anilist.type.MediaFormat
import dev.datlag.aniflow.anilist.type.MediaStatus
import dev.datlag.aniflow.common.shimmerPainter
import dev.datlag.aniflow.common.text
import dev.datlag.tooling.compose.ifTrue
import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.StateFlow

@Composable
fun CoverSection(
coverImage: Medium.CoverImage,
formatFlow: StateFlow<MediaFormat>,
episodesFlow: StateFlow<Int>,
durationFlow: StateFlow<Int>,
statusFlow: StateFlow<MediaStatus>,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
var coverShadow by remember(coverImage) { mutableStateOf(false) }

AsyncImage(
modifier = Modifier
.width(140.dp)
.height(200.dp)
.ifTrue(coverShadow) {
shadow(
elevation = 8.dp,
shape = MaterialTheme.shapes.medium,
spotColor = MaterialTheme.colorScheme.primary
)
},
model = coverImage.extraLarge,
contentScale = ContentScale.Crop,
contentDescription = null,
placeholder = shimmerPainter(),
error = rememberAsyncImagePainter(
model = coverImage.large,
contentScale = ContentScale.Crop,
placeholder = shimmerPainter(),
error = rememberAsyncImagePainter(
model = coverImage.medium,
contentScale = ContentScale.Crop,
placeholder = shimmerPainter(),
onSuccess = {
coverShadow = true
}
),
onSuccess = {
coverShadow = true
}
),
onSuccess = {
coverShadow = true
}
)
Column(
modifier = Modifier.weight(1F).fillMaxHeight(),
verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically)
) {
val format by formatFlow.collectAsStateWithLifecycle()
val episodes by episodesFlow.collectAsStateWithLifecycle()
val duration by durationFlow.collectAsStateWithLifecycle()
val status by statusFlow.collectAsStateWithLifecycle()

Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = Icons.Default.OndemandVideo,
contentDescription = null
)
Text(text = stringResource(format.text()))
}
if (episodes > -1) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.List,
contentDescription = null
)
Text(text = "$episodes Episodes")
}
}
if (duration > -1) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = Icons.Default.Timelapse,
contentDescription = null
)
Text(text = "${duration}min / Episode")
}
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = Icons.Default.RssFeed,
contentDescription = null
)
Text(text = stringResource(status.text()))
}
}
}
}
Loading

0 comments on commit 9ea4d3f

Please sign in to comment.