diff --git a/app/src/main/java/org/xtimms/tokusho/core/Navigation.kt b/app/src/main/java/org/xtimms/tokusho/core/Navigation.kt
index c8dffc7..d4e32f3 100644
--- a/app/src/main/java/org/xtimms/tokusho/core/Navigation.kt
+++ b/app/src/main/java/org/xtimms/tokusho/core/Navigation.kt
@@ -17,6 +17,8 @@ import coil.ImageLoader
import org.xtimms.tokusho.core.model.ShelfCategory
import org.xtimms.tokusho.core.motion.materialSharedAxisXIn
import org.xtimms.tokusho.core.motion.materialSharedAxisXOut
+import org.xtimms.tokusho.sections.details.DETAILS_DESTINATION
+import org.xtimms.tokusho.sections.details.DetailsView
import org.xtimms.tokusho.sections.explore.ExploreView
import org.xtimms.tokusho.sections.history.HistoryView
import org.xtimms.tokusho.sections.list.LIST_DESTINATION
@@ -154,6 +156,7 @@ fun Navigation(
MangaListView(
sourceName = "Source",
navigateBack = navigateBack,
+ navigateToDetails = { navController.navigate(DETAILS_DESTINATION) }
)
}
@@ -169,6 +172,12 @@ fun Navigation(
navigateBack = navigateBack,
)
}
+
+ composable(DETAILS_DESTINATION) {
+ DetailsView(
+ navigateBack = navigateBack,
+ )
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/core/components/DetailsToolbar.kt b/app/src/main/java/org/xtimms/tokusho/core/components/DetailsToolbar.kt
new file mode 100644
index 0000000..710dfb4
--- /dev/null
+++ b/app/src/main/java/org/xtimms/tokusho/core/components/DetailsToolbar.kt
@@ -0,0 +1,49 @@
+package org.xtimms.tokusho.core.components
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun DetailsToolbar(
+ title: String,
+ titleAlphaProvider: () -> Float,
+ onBackClicked: () -> Unit,
+ modifier: Modifier = Modifier,
+ backgroundAlphaProvider: () -> Float = titleAlphaProvider
+) {
+ Column(
+ modifier = modifier,
+ ) {
+ TopAppBar(
+ title = {
+ Text(
+ text = title,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ color = LocalContentColor.current.copy(alpha = titleAlphaProvider()),
+ )
+ },
+ navigationIcon = {
+ BackIconButton(
+ onClick = onBackClicked
+ )
+ },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme
+ .surfaceColorAtElevation(3.dp)
+ .copy(alpha = backgroundAlphaProvider())
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/core/components/MangaCover.kt b/app/src/main/java/org/xtimms/tokusho/core/components/MangaCover.kt
new file mode 100644
index 0000000..2df1f62
--- /dev/null
+++ b/app/src/main/java/org/xtimms/tokusho/core/components/MangaCover.kt
@@ -0,0 +1,50 @@
+package org.xtimms.tokusho.core.components
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.semantics.Role
+
+enum class MangaCover(val ratio: Float) {
+ Square(1f / 1f),
+ Book(10f / 16f),
+ ;
+
+ @Composable
+ operator fun invoke(
+ data: Painter,
+ modifier: Modifier = Modifier,
+ contentDescription: String = "",
+ shape: Shape = MaterialTheme.shapes.small,
+ onClick: (() -> Unit)? = null,
+ ) {
+ Image(
+ painter = data,
+ contentDescription = contentDescription,
+ modifier = modifier
+ .aspectRatio(ratio)
+ .clip(shape)
+ .then(
+ if (onClick != null) {
+ Modifier.clickable(
+ role = Role.Button,
+ onClick = onClick,
+ )
+ } else {
+ Modifier
+ },
+ ),
+ contentScale = ContentScale.Crop,
+ )
+ }
+}
+
+private val CoverPlaceholderColor = Color(0x1F888888)
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsInfoHeader.kt b/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsInfoHeader.kt
new file mode 100644
index 0000000..d5bd723
--- /dev/null
+++ b/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsInfoHeader.kt
@@ -0,0 +1,326 @@
+package org.xtimms.tokusho.sections.details
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+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.sizeIn
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.MenuBook
+import androidx.compose.material.icons.outlined.Block
+import androidx.compose.material.icons.outlined.Brush
+import androidx.compose.material.icons.outlined.Close
+import androidx.compose.material.icons.outlined.DoneAll
+import androidx.compose.material.icons.outlined.Language
+import androidx.compose.material.icons.outlined.MenuBook
+import androidx.compose.material.icons.outlined.Pause
+import androidx.compose.material.icons.outlined.Person
+import androidx.compose.material.icons.outlined.Schedule
+import androidx.compose.material.icons.outlined.Upcoming
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ProvideTextStyle
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.blur
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.PreviewLightDark
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import org.koitharu.kotatsu.parsers.model.MangaState
+import org.xtimms.tokusho.R
+import org.xtimms.tokusho.core.components.MangaCover
+import org.xtimms.tokusho.ui.theme.TokushoTheme
+import org.xtimms.tokusho.utils.secondaryItemAlpha
+
+@Composable
+fun DetailsInfoBox(
+ isTabletUi: Boolean,
+ appBarPadding: Dp,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier = modifier) {
+ val backdropGradientColors = listOf(
+ Color.Transparent,
+ MaterialTheme.colorScheme.background,
+ )
+ Image(
+ painterResource(id = R.drawable.ookami),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .matchParentSize()
+ .drawWithContent {
+ drawContent()
+ drawRect(
+ brush = Brush.verticalGradient(colors = backdropGradientColors),
+ )
+ }
+ .blur(8.dp)
+ .alpha(0.2f)
+ )
+
+ CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
+ if (!isTabletUi) {
+ MangaAndSourceTitlesSmall(
+ appBarPadding = appBarPadding,
+ onCoverClick = { },
+ title = "Ookami to Koushinryou",
+ author = "Hasekura Isuna",
+ artist = "Koume Keito",
+ state = MangaState.FINISHED
+ )
+ } else {
+ MangaAndSourceTitlesLarge(
+ appBarPadding = appBarPadding,
+ onCoverClick = { },
+ title = "Ookami to Koushinryou",
+ author = "Hasekura Isuna",
+ artist = "Koume Keito",
+ state = MangaState.FINISHED
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun MangaAndSourceTitlesLarge(
+ appBarPadding: Dp,
+ onCoverClick: () -> Unit,
+ title: String,
+ author: String?,
+ artist: String?,
+ state: MangaState?
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 16.dp, top = appBarPadding + 16.dp, end = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ MangaCover.Book(
+ modifier = Modifier.fillMaxWidth(0.65f),
+ data = painterResource(id = R.drawable.ookami),
+ contentDescription = stringResource(R.string.manga_cover),
+ onClick = onCoverClick,
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ DetailsContentInfo(
+ title = title,
+ author = author,
+ artist = artist,
+ )
+ }
+}
+
+@Composable
+private fun MangaAndSourceTitlesSmall(
+ appBarPadding: Dp,
+ onCoverClick: () -> Unit,
+ title: String,
+ author: String?,
+ artist: String?,
+ state: MangaState?,
+) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 16.dp, top = appBarPadding + 16.dp, end = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ MangaCover.Book(
+ modifier = Modifier
+ .sizeIn(maxWidth = 100.dp)
+ .align(Alignment.Top),
+ data = painterResource(id = R.drawable.ookami),
+ contentDescription = stringResource(R.string.manga_cover),
+ onClick = onCoverClick,
+ )
+ Column(
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ DetailsContentInfo(
+ title = title,
+ author = author,
+ artist = artist,
+ )
+ }
+ }
+ Row {
+ DetailsRow(
+ source = "MangaDex",
+ chapters = "22 chapters",
+ state = state
+ )
+ }
+ }
+
+}
+
+@Composable
+private fun ColumnScope.DetailsContentInfo(
+ title: String,
+ author: String?,
+ artist: String?,
+ textAlign: TextAlign? = LocalTextStyle.current.textAlign,
+) {
+ val context = LocalContext.current
+ Text(
+ text = title.ifBlank { stringResource(id = R.string.unknown_title) },
+ style = MaterialTheme.typography.headlineSmall,
+ textAlign = textAlign
+ )
+
+ Spacer(modifier = Modifier.height(2.dp))
+
+ Row(
+ modifier = Modifier.secondaryItemAlpha(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Person,
+ contentDescription = null,
+ modifier = Modifier.size(16.dp)
+ )
+ Text(
+ text = author?.takeIf { it.isNotBlank() }
+ ?: stringResource(id = R.string.unknown_author),
+ style = MaterialTheme.typography.titleSmall,
+ textAlign = textAlign
+ )
+ }
+
+ if (!artist.isNullOrBlank() && author != artist) {
+ Row(
+ modifier = Modifier.secondaryItemAlpha(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Brush,
+ contentDescription = null,
+ modifier = Modifier.size(16.dp)
+ )
+ Text(
+ text = artist,
+ style = MaterialTheme.typography.titleSmall,
+ textAlign = textAlign,
+ )
+ }
+ }
+}
+
+@Composable
+private fun RowScope.DetailsRow(
+ source: String?,
+ chapters: String?,
+ state: MangaState?,
+ textAlign: TextAlign? = LocalTextStyle.current.textAlign,
+) {
+ Column(
+ modifier = Modifier.weight(1f).wrapContentSize().secondaryItemAlpha(),
+ verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Icon(
+ imageVector = when (state) {
+ MangaState.ONGOING -> Icons.Outlined.Schedule
+ MangaState.FINISHED -> Icons.Outlined.DoneAll
+ MangaState.ABANDONED -> Icons.Outlined.Close
+ MangaState.PAUSED -> Icons.Outlined.Pause
+ MangaState.UPCOMING -> Icons.Outlined.Upcoming
+ else -> Icons.Outlined.Block
+ },
+ contentDescription = null,
+ modifier = Modifier
+ .size(24.dp),
+ )
+ ProvideTextStyle(MaterialTheme.typography.bodySmall) {
+ Text(
+ text = when (state) {
+ MangaState.ONGOING -> stringResource(id = R.string.ongoing)
+ MangaState.FINISHED -> stringResource(id = R.string.finished)
+ MangaState.ABANDONED -> stringResource(id = R.string.abandoned)
+ MangaState.PAUSED -> stringResource(id = R.string.paused)
+ MangaState.UPCOMING -> stringResource(id = R.string.upcoming)
+ else -> stringResource(id = R.string.unknown)
+ },
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+ }
+ }
+ Column(
+ modifier = Modifier.weight(1f).wrapContentSize().secondaryItemAlpha(),
+ verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Outlined.MenuBook,
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ Text(
+ text = chapters?.takeIf { it.isNotBlank() }
+ ?: stringResource(id = R.string.unknown),
+ style = MaterialTheme.typography.bodySmall,
+ textAlign = textAlign
+ )
+ }
+ Column(
+ modifier = Modifier.weight(1f).wrapContentSize().secondaryItemAlpha(),
+ verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterVertically),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Language,
+ contentDescription = null,
+ modifier = Modifier.size(24.dp)
+ )
+ Text(
+ text = source?.takeIf { it.isNotBlank() }
+ ?: stringResource(id = R.string.unknown),
+ style = MaterialTheme.typography.bodySmall,
+ textAlign = textAlign
+ )
+ }
+}
+
+@PreviewLightDark
+@Composable
+fun DetailsInfoBoxPreview() {
+ TokushoTheme {
+ DetailsInfoBox(
+ isTabletUi = false,
+ appBarPadding = 72.dp,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsView.kt b/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsView.kt
new file mode 100644
index 0000000..151222f
--- /dev/null
+++ b/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsView.kt
@@ -0,0 +1,78 @@
+package org.xtimms.tokusho.sections.details
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalLayoutDirection
+import org.xtimms.tokusho.core.components.DetailsToolbar
+
+const val DETAILS_DESTINATION = "details"
+
+@Composable
+fun DetailsView(
+ navigateBack: () -> Unit,
+) {
+
+ val chapterListState = rememberLazyListState()
+
+ Scaffold(
+ topBar = {
+ val isFirstItemVisible by remember {
+ derivedStateOf { chapterListState.firstVisibleItemIndex == 0 }
+ }
+ val isFirstItemScrolled by remember {
+ derivedStateOf { chapterListState.firstVisibleItemScrollOffset > 0 }
+ }
+ val animatedTitleAlpha by animateFloatAsState(
+ if (!isFirstItemVisible) 1f else 0f,
+ label = "Top Bar Title",
+ )
+ val animatedBgAlpha by animateFloatAsState(
+ if (!isFirstItemVisible || isFirstItemScrolled) 1f else 0f,
+ label = "Top Bar Background",
+ )
+ DetailsToolbar(
+ title = "Test",
+ titleAlphaProvider = { animatedTitleAlpha },
+ backgroundAlphaProvider = { animatedBgAlpha },
+ onBackClicked = { navigateBack() }
+ )
+ },
+ bottomBar = {
+
+ },
+ ) { contentPadding ->
+ val topPadding = contentPadding.calculateTopPadding()
+ val layoutDirection = LocalLayoutDirection.current
+ LazyColumn(
+ modifier = Modifier.fillMaxHeight(),
+ state = chapterListState,
+ contentPadding = PaddingValues(
+ start = contentPadding.calculateStartPadding(layoutDirection),
+ end = contentPadding.calculateEndPadding(layoutDirection),
+ bottom = contentPadding.calculateBottomPadding(),
+ ),
+ ) {
+ item(
+ key = DetailsViewItem.INFO_BOX,
+ contentType = DetailsViewItem.INFO_BOX
+ ) {
+ DetailsInfoBox(
+ isTabletUi = false,
+ appBarPadding = topPadding,
+ )
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsViewConstants.kt b/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsViewConstants.kt
new file mode 100644
index 0000000..c63972a
--- /dev/null
+++ b/app/src/main/java/org/xtimms/tokusho/sections/details/DetailsViewConstants.kt
@@ -0,0 +1,9 @@
+package org.xtimms.tokusho.sections.details
+
+enum class DetailsViewItem {
+ INFO_BOX,
+ ACTION_ROW,
+ DESCRIPTION_WITH_TAG,
+ CHAPTER_HEADER,
+ CHAPTER,
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/xtimms/tokusho/sections/list/MangaListView.kt b/app/src/main/java/org/xtimms/tokusho/sections/list/MangaListView.kt
index 4e11f41..040d276 100644
--- a/app/src/main/java/org/xtimms/tokusho/sections/list/MangaListView.kt
+++ b/app/src/main/java/org/xtimms/tokusho/sections/list/MangaListView.kt
@@ -4,6 +4,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -15,6 +17,7 @@ const val LIST_DESTINATION = "list"
fun MangaListView(
sourceName: String,
navigateBack: () -> Unit,
+ navigateToDetails: () -> Unit,
) {
val scrollState = rememberScrollState()
@@ -29,7 +32,9 @@ fun MangaListView(
.padding(padding),
horizontalAlignment = Alignment.CenterHorizontally
) {
-
+ Button(onClick = { navigateToDetails() }) {
+ Text(text = "Click")
+ }
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9a26e25..bcaa6f9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -59,4 +59,13 @@
No manga in this category
README
Check the Gitea repository and the README
+ Manga cover
+ Unknown title
+ Unknown author
+ Ongoing
+ Finished
+ Abandoned
+ Paused
+ Upcoming
+ Unknown
\ No newline at end of file