diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d6a6235c6..b789687bb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -189,7 +189,6 @@ dependencies { implementation(libs.androidx.core) implementation(libs.androidx.core.splashscreen) - implementation(libs.androidx.constraintlayout.compose) implementation(libs.androidx.datastore) implementation(libs.androidx.graphics.path) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt index 072f98022..58297e3b6 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt @@ -4,9 +4,13 @@ import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource 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.aspectRatio import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Pause @@ -31,15 +35,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.layoutId import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.ConstraintSet -import androidx.constraintlayout.compose.Dimension -import arrow.core.Tuple9 import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhUtils @@ -51,51 +49,6 @@ import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope import com.hippo.ehviewer.ui.tools.listThumbGenerator import com.hippo.ehviewer.util.FileUtils -private val ids = Tuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) - -private val constraintSet = ConstraintSet { - val ( - titleRef, uploaderRef, ratingRef, categoryRef, actionsRef, stateTextRef, - progressBarRef, progressTextRef, speedRef, - ) = createRefsFor(1, 2, 3, 4, 5, 6, 7, 8, 9) - constrain(titleRef) { - top.linkTo(parent.top) - width = Dimension.matchParent - } - constrain(uploaderRef) { - start.linkTo(parent.start) - bottom.linkTo(actionsRef.top) - } - constrain(ratingRef) { - start.linkTo(parent.start) - top.linkTo(actionsRef.top, 1.dp) - } - constrain(categoryRef) { - start.linkTo(parent.start) - bottom.linkTo(parent.bottom) - } - constrain(progressBarRef) { - bottom.linkTo(actionsRef.top) - width = Dimension.matchParent - } - constrain(progressTextRef) { - bottom.linkTo(progressBarRef.top) - start.linkTo(progressBarRef.start) - } - constrain(speedRef) { - bottom.linkTo(progressBarRef.top) - end.linkTo(progressBarRef.end) - } - constrain(actionsRef) { - end.linkTo(parent.end, (-4).dp) - bottom.linkTo(parent.bottom, (-4).dp) - } - constrain(stateTextRef) { - end.linkTo(parent.end) - bottom.linkTo(actionsRef.top) - } -} - context(_: SharedTransitionScope, _: TransitionsVisibilityScope) @Composable fun DownloadCard( @@ -132,52 +85,34 @@ fun DownloadCard( val stateFailed = stringResource(R.string.download_state_failed) val stateFailed2 = stringResource(R.string.download_state_failed_2, info.legacy) val downloadState by DownloadManager.collectDownloadState(info.gid) - val stateText = when (downloadState) { - DownloadInfo.STATE_NONE -> stringResource(R.string.download_state_none) - DownloadInfo.STATE_WAIT -> stringResource(R.string.download_state_wait) - DownloadInfo.STATE_DOWNLOAD -> null - DownloadInfo.STATE_FAILED -> if (info.legacy <= 0) stateFailed else stateFailed2 - DownloadInfo.STATE_FINISH -> stringResource(R.string.download_state_finish) - else -> null // Chill, will be removed soon - } - ConstraintLayout( - modifier = Modifier.padding(start = 8.dp, top = 2.dp, end = 4.dp, bottom = 4.dp).fillMaxSize(), - constraintSet = constraintSet, - ) { - val ( - titleRef, uploaderRef, ratingRef, categoryRef, actionsRef, stateTextRef, - progressBarRef, progressTextRef, speedRef, - ) = ids + Column(modifier = Modifier.padding(start = 8.dp, top = 2.dp, end = 4.dp)) { Text( text = EhUtils.getSuitableTitle(info), maxLines = 2, - modifier = Modifier.layoutId(titleRef), overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleSmall, ) + Spacer(modifier = Modifier.weight(1f)) if (downloadState != DownloadInfo.STATE_DOWNLOAD) { + val stateText = when (downloadState) { + DownloadInfo.STATE_NONE -> stringResource(R.string.download_state_none) + DownloadInfo.STATE_WAIT -> stringResource(R.string.download_state_wait) + DownloadInfo.STATE_FAILED -> if (info.legacy <= 0) stateFailed else stateFailed2 + DownloadInfo.STATE_FINISH -> stringResource(R.string.download_state_finish) + else -> null // The item has been removed and this will be disposed soon + } ProvideTextStyle(MaterialTheme.typography.labelLarge) { - info.uploader?.let { + Row { Text( - text = it, - modifier = Modifier.layoutId(uploaderRef).alpha(if (info.disowned) 0.5f else 1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, + text = info.uploader.orEmpty(), + modifier = Modifier.alignByBaseline().alpha(if (info.disowned) 0.5f else 1f), + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + text = stateText.orEmpty(), + modifier = Modifier.alignByBaseline(), ) } - GalleryListCardRating( - rating = info.rating, - modifier = Modifier.layoutId(ratingRef), - ) - val categoryColor = EhUtils.getCategoryColor(info.category) - val categoryText = EhUtils.getCategory(info.category).uppercase() - Text( - text = categoryText, - modifier = Modifier.layoutId(categoryRef).clip(ShapeDefaults.Small) - .background(categoryColor).padding(vertical = 2.dp, horizontal = 8.dp), - color = if (Settings.harmonizeCategoryColor) Color.Unspecified else EhUtils.categoryTextColor, - textAlign = TextAlign.Center, - ) } } else { var total by remember { mutableIntStateOf(info.total) } @@ -190,44 +125,49 @@ fun DownloadCard( } ProvideTextStyle(MaterialTheme.typography.labelMedium) { if (total <= 0 || finished < 0) { - LinearWavyProgressIndicator(modifier = Modifier.layoutId(progressBarRef)) + LinearWavyProgressIndicator(modifier = Modifier.fillMaxWidth()) } else { + Row { + Text(text = "$finished/$total") + Spacer(modifier = Modifier.weight(1f)) + Text(text = FileUtils.humanReadableByteCount(speed.coerceAtLeast(0)) + "/S") + } LinearWavyProgressIndicator( progress = { finished.toFloat() / total.toFloat() }, - modifier = Modifier.layoutId(progressBarRef), - ) - Text( - text = "$finished/$total", - modifier = Modifier.layoutId(progressTextRef), + modifier = Modifier.fillMaxWidth(), ) } + } + } + Row { + Column { + // Place the rating near the uploader text as there's more visual space + GalleryListCardRating(rating = info.rating, modifier = Modifier.padding(top = 1.dp, bottom = 3.dp)) + val categoryColor = EhUtils.getCategoryColor(info.category) + val categoryText = EhUtils.getCategory(info.category).uppercase() Text( - text = FileUtils.humanReadableByteCount(speed.coerceAtLeast(0)) + "/S", - modifier = Modifier.layoutId(speedRef), + text = categoryText, + modifier = Modifier.clip(ShapeDefaults.Small).background(categoryColor).padding(vertical = 2.dp, horizontal = 8.dp), + color = if (Settings.harmonizeCategoryColor) Color.Unspecified else EhUtils.categoryTextColor, + style = MaterialTheme.typography.labelLarge, ) } - } - val running = downloadState == DownloadInfo.STATE_WAIT || downloadState == DownloadInfo.STATE_DOWNLOAD - val icon = remember { - movableContentOf { - Icon(imageVector = if (it) Icons.Default.Pause else Icons.Default.PlayArrow, contentDescription = null) + Spacer(modifier = Modifier.weight(1f)) + val running = downloadState == DownloadInfo.STATE_WAIT || downloadState == DownloadInfo.STATE_DOWNLOAD + val icon = remember { + movableContentOf { + Icon(imageVector = if (it) Icons.Default.Pause else Icons.Default.PlayArrow, contentDescription = null) + } } - } - if (selectMode) { - Box(modifier = Modifier.layoutId(actionsRef).minimumInteractiveComponentSize()) { - icon(running) + if (selectMode) { + Box(modifier = Modifier.offset(4.dp).minimumInteractiveComponentSize()) { + icon(running) + } + } else { + IconButton(onClick = if (running) onStop else onStart, modifier = Modifier.offset(4.dp)) { + icon(running) + } } - } else { - IconButton(onClick = if (running) onStop else onStart, modifier = Modifier.layoutId(actionsRef)) { - icon(running) - } - } - if (stateText != null) { - Text( - text = stateText, - modifier = Modifier.layoutId(stateTextRef), - style = MaterialTheme.typography.labelLarge, - ) } } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryDetail.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryDetail.kt index 5be632be8..d2b92cf73 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryDetail.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryDetail.kt @@ -4,12 +4,14 @@ import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Label @@ -29,7 +31,6 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout import com.hippo.ehviewer.R import com.hippo.ehviewer.client.EhUtils import com.hippo.ehviewer.client.data.GalleryDetail @@ -47,53 +48,29 @@ fun GalleryDetailHeaderInfoCard( ) = detail.run { Card( onClick = onClick, - modifier = modifier, + modifier = modifier.width(IntrinsicSize.Max), ) { - ConstraintLayout(modifier = Modifier.padding(8.dp)) { - val (langRef, sizeRef, favRef, pagesRef, postRef) = createRefs() - - ProvideTextStyle(MaterialTheme.typography.labelMedium) { - Text( - text = language.orEmpty(), - modifier = Modifier.constrainAs(langRef) { - top.linkTo(parent.top) - start.linkTo(parent.start) - }, - ) - Text( - text = size.orEmpty(), - modifier = Modifier.constrainAs(sizeRef) { - baseline.linkTo(langRef.baseline) - end.linkTo(parent.end) - horizontalBias = 1.0f - start.linkTo(langRef.end) - }.padding(start = 16.dp), - ) + ProvideTextStyle(MaterialTheme.typography.labelMedium) { + Row(modifier = Modifier.padding(8.dp)) { + Text(text = language.orEmpty()) + Spacer(modifier = Modifier.width(16.dp).weight(1f)) + Text(text = size.orEmpty()) + } + Row(modifier = Modifier.padding(horizontal = 8.dp)) { Text( text = stringResource(id = R.string.favored_times, favoriteCount), - modifier = Modifier.constrainAs(favRef) { - start.linkTo(parent.start) - top.linkTo(langRef.bottom) - }.padding(top = 8.dp), + modifier = Modifier.alignByBaseline(), ) + Spacer(modifier = Modifier.width(16.dp).weight(1f)) Text( text = pluralStringResource(id = R.plurals.page_count, pages, pages), - modifier = Modifier.constrainAs(pagesRef) { - baseline.linkTo(favRef.baseline) - end.linkTo(parent.end) - horizontalBias = 1.0f - start.linkTo(favRef.end) - }.padding(start = 16.dp), - ) - Text( - text = posted.orEmpty(), - modifier = Modifier.constrainAs(postRef) { - end.linkTo(parent.end) - start.linkTo(parent.start) - top.linkTo(favRef.bottom) - }.padding(top = 8.dp), + modifier = Modifier.alignByBaseline(), ) } + Text( + text = posted.orEmpty(), + modifier = Modifier.align(Alignment.CenterHorizontally).padding(8.dp), + ) } } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryInfo.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryInfo.kt index 5d7a5a4c2..61be6e67b 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryInfo.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryInfo.kt @@ -5,10 +5,10 @@ import androidx.compose.foundation.background 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.Spacer import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -34,15 +34,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.layoutId import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.ConstraintSet -import androidx.constraintlayout.compose.Dimension -import arrow.core.Tuple7 import coil3.compose.AsyncImage import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhUtils @@ -57,41 +51,6 @@ import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope import com.hippo.ehviewer.ui.tools.listThumbGenerator import com.hippo.ehviewer.util.FavouriteStatusRouter -private val ids = Tuple7(1, 2, 3, 4, 5, 6, 7) - -private val constraintSet = ConstraintSet { - val (titleRef, uploaderRef, ratingRef, categoryRef, postedRef, favRef, iconsRef) = - createRefsFor(1, 2, 3, 4, 5, 6, 7) - constrain(titleRef) { - top.linkTo(parent.top) - width = Dimension.matchParent - } - constrain(uploaderRef) { - start.linkTo(parent.start) - bottom.linkTo(iconsRef.top) - } - constrain(ratingRef) { - start.linkTo(parent.start) - top.linkTo(iconsRef.top, 1.dp) - } - constrain(categoryRef) { - start.linkTo(parent.start) - bottom.linkTo(parent.bottom) - } - constrain(iconsRef) { - end.linkTo(parent.end) - bottom.linkTo(categoryRef.top) - } - constrain(favRef) { - end.linkTo(parent.end) - bottom.linkTo(iconsRef.top) - } - constrain(postedRef) { - end.linkTo(parent.end) - linkTo(categoryRef.top, parent.bottom) - } -} - context(_: SharedTransitionScope, _: TransitionsVisibilityScope) @Composable fun GalleryInfoListItem( @@ -115,48 +74,48 @@ fun GalleryInfoListItem( modifier = Modifier.aspectRatio(DEFAULT_RATIO), ) } - ConstraintLayout( - modifier = Modifier.padding(start = 8.dp, top = 2.dp, end = 4.dp, bottom = 4.dp).fillMaxSize(), - constraintSet = constraintSet, - ) { - val (titleRef, uploaderRef, ratingRef, categoryRef, postedRef, favRef, iconsRef) = ids + Column(modifier = Modifier.padding(start = 8.dp, top = 2.dp, end = 4.dp, bottom = 4.dp)) { Text( text = EhUtils.getSuitableTitle(info), maxLines = 2, - modifier = Modifier.layoutId(titleRef), overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleSmall, ) + Spacer(modifier = Modifier.weight(1f)) ProvideTextStyle(MaterialTheme.typography.labelLarge) { - info.uploader?.let { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { Text( - text = it, - modifier = Modifier.layoutId(uploaderRef).alpha(if (info.disowned) 0.5f else 1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, + text = info.uploader.orEmpty(), + modifier = Modifier.alignByBaseline().alpha(if (info.disowned) 0.5f else 1f), ) + Spacer(modifier = Modifier.weight(1f)) + if (isInFavScene) { + info.favoriteNote?.let { + Text(text = it, modifier = Modifier.alignByBaseline(), fontStyle = FontStyle.Italic) + } + } else { + val showFav by FavouriteStatusRouter.collectAsState(info) { it != NOT_FAVORITED } + if (showFav) { + Icon( + Icons.Default.Favorite, + contentDescription = null, + modifier = Modifier.size(16.dp).align(Alignment.CenterVertically), + ) + info.favoriteName?.let { + Text(text = it, modifier = Modifier.alignByBaseline()) + } + } + } } - GalleryListCardRating( - rating = info.rating, - modifier = Modifier.layoutId(ratingRef), - ) - val categoryColor = EhUtils.getCategoryColor(info.category) - val categoryText = EhUtils.getCategory(info.category).uppercase() - Text( - text = categoryText, - modifier = Modifier.layoutId(categoryRef).clip(ShapeDefaults.Small).background(categoryColor).padding(vertical = 2.dp, horizontal = 8.dp), - color = if (Settings.harmonizeCategoryColor) Color.Unspecified else EhUtils.categoryTextColor, - textAlign = TextAlign.Center, - ) Row( horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.layoutId(iconsRef), ) { - // Placeholder to reserve minimum height - Text(text = "") - val download by DownloadManager.collectContainDownloadInfo(info.gid) - if (download) { + // Place the rating near the uploader text as there's more visual space + GalleryListCardRating(rating = info.rating, modifier = Modifier.padding(top = 1.dp, bottom = 3.dp)) + Spacer(modifier = Modifier.weight(1f)) + val downloaded by DownloadManager.collectContainDownloadInfo(info.gid) + if (downloaded) { Icon( Icons.Default.Download, contentDescription = null, @@ -170,35 +129,17 @@ fun GalleryInfoListItem( Text(text = "${info.pages}P") } } - Row( - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.layoutId(favRef), - ) { - // Placeholder to reserve minimum height - Text(text = "") - if (isInFavScene) { - info.favoriteNote?.let { - Text(text = it, fontStyle = FontStyle.Italic) - } - } else { - val showFav by FavouriteStatusRouter.collectAsState(info) { it != NOT_FAVORITED } - if (showFav) { - Icon( - Icons.Default.Favorite, - contentDescription = null, - modifier = Modifier.size(16.dp), - ) - info.favoriteName?.let { - Text(text = it) - } - } - } + Row(verticalAlignment = Alignment.CenterVertically) { + val categoryColor = EhUtils.getCategoryColor(info.category) + val categoryText = EhUtils.getCategory(info.category).uppercase() + Text( + text = categoryText, + modifier = Modifier.clip(ShapeDefaults.Small).background(categoryColor).padding(vertical = 2.dp, horizontal = 8.dp), + color = if (Settings.harmonizeCategoryColor) Color.Unspecified else EhUtils.categoryTextColor, + ) + Spacer(modifier = Modifier.weight(1f)) + Text(text = info.posted.orEmpty()) } - Text( - text = info.posted.orEmpty(), - modifier = Modifier.layoutId(postedRef), - ) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ee6149dbd..7574652a3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,6 @@ androidx-activity = "1.10.1" androidx-appcompat = "1.7.0" androidx-biometric = "1.4.0-alpha04" androidx-browser = "1.8.0" -androidx-constraintlayout-compose = "1.1.1" androidx-core = "1.16.0" androidx-core-splashscreen = "1.2.0-beta02" androidx-datastore = "1.1.7" @@ -44,7 +43,6 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "androidx-biometric" } androidx-browser = { module = "androidx.browser:browser", version.ref = "androidx-browser" } -androidx-constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "androidx-constraintlayout-compose" } androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" } androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "androidx-datastore" }