-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
split medium screen into section to improve performance
- Loading branch information
Showing
8 changed files
with
680 additions
and
413 deletions.
There are no files selected for viewing
458 changes: 45 additions & 413 deletions
458
...eApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/MediumScreen.kt
Large diffs are not rendered by default.
Oops, something went wrong.
53 changes: 53 additions & 0 deletions
53
...nMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CharacterSection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} |
147 changes: 147 additions & 0 deletions
147
...Main/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CollapsingToolbar.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
) | ||
} | ||
} |
140 changes: 140 additions & 0 deletions
140
...ommonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/medium/component/CoverSection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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())) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.