diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/EhAsyncThumb.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/EhAsyncThumb.kt index 9a8f715cb..3701412a0 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/EhAsyncThumb.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/EhAsyncThumb.kt @@ -14,10 +14,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import coil3.Image as CoilImage import coil3.compose.AsyncImage import coil3.compose.AsyncImagePainter import coil3.compose.ConstraintsSizeResolver @@ -27,8 +25,8 @@ import coil3.request.ImageRequest import com.hippo.ehviewer.client.data.GalleryInfo import com.hippo.ehviewer.ktbuilder.imageRequest import com.hippo.ehviewer.ui.tools.SETNodeGenerator +import com.hippo.ehviewer.ui.tools.SharedElementBox import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope -import com.hippo.ehviewer.ui.tools.sharedBounds import com.hippo.ehviewer.ui.tools.shouldCrop import com.hippo.ehviewer.ui.tools.thenIf @@ -57,35 +55,17 @@ fun Modifier.imageRequest(request: ImageRequest): Modifier { } } -context(SharedTransitionScope, TransitionsVisibilityScope, SETNodeGenerator) -@Composable -@NonRestartableComposable -fun EhAsyncThumb( - model: GalleryInfo, - modifier: Modifier = Modifier, - onSuccess: ((CoilImage) -> Unit)? = null, - contentScale: ContentScale = ContentScale.Fit, -) = AsyncImage( - model = requestOf(model), - contentDescription = null, - modifier = modifier.sharedBounds(key = "${model.gid}").clip(ShapeDefaults.Medium), - onSuccess = onSuccess?.let { callback -> - { callback(it.result.image) } - }, - contentScale = contentScale, -) - context(SharedTransitionScope, TransitionsVisibilityScope, SETNodeGenerator) @Composable fun EhAsyncCropThumb( key: GalleryInfo, modifier: Modifier = Modifier, -) { +) = SharedElementBox(key = "${key.gid}", shape = ShapeDefaults.Medium) { var contentScale by remember(key) { mutableStateOf(ContentScale.Fit) } AsyncImage( model = requestOf(key), contentDescription = null, - modifier = modifier.sharedBounds(key = "${key.gid}").clip(ShapeDefaults.Medium), + modifier = modifier, onSuccess = { if (it.result.image.shouldCrop) { contentScale = ContentScale.Crop @@ -100,28 +80,26 @@ context(SharedTransitionScope, TransitionsVisibilityScope, SETNodeGenerator) fun EhThumbCard( key: GalleryInfo, modifier: Modifier = Modifier, -) { - var contentScale by remember(key) { mutableStateOf(ContentScale.Fit) } - val request = requestOf(key) - val painter = rememberAsyncImagePainter( - model = request, - onSuccess = { - if (it.result.image.shouldCrop) { - contentScale = ContentScale.Crop - } - }, - ) - val state by painter.state.collectAsState() - Card(modifier = modifier) { +) = Card(modifier = modifier) { + SharedElementBox(key = "${key.gid}", shape = ShapeDefaults.Medium) { + var contentScale by remember(key) { mutableStateOf(ContentScale.Fit) } + val request = requestOf(key) + val painter = rememberAsyncImagePainter( + model = request, + onSuccess = { + if (it.result.image.shouldCrop) { + contentScale = ContentScale.Crop + } + }, + ) + val state by painter.state.collectAsState() Image( painter = painter, contentDescription = null, modifier = Modifier.thenIf(state !is AsyncImagePainter.State.Success) { // Keep applying this when state is `Loading` to avoid cutting off the ripple - clickable { - if (state is AsyncImagePainter.State.Error) painter.restart() - } - }.imageRequest(request).fillMaxSize().sharedBounds(key = "${key.gid}").clip(ShapeDefaults.Medium), + clickable { if (state is AsyncImagePainter.State.Error) painter.restart() } + }.imageRequest(request).fillMaxSize(), contentScale = contentScale, ) } 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 40b4a1cbd..5ebafcf73 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 @@ -43,6 +43,7 @@ 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 import com.hippo.ehviewer.client.data.GalleryInfo @@ -51,6 +52,7 @@ import com.hippo.ehviewer.download.DownloadManager import com.hippo.ehviewer.ui.tools.CrystalCard import com.hippo.ehviewer.ui.tools.ElevatedCard import com.hippo.ehviewer.ui.tools.GalleryListCardRating +import com.hippo.ehviewer.ui.tools.SharedElementBox import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope import com.hippo.ehviewer.ui.tools.listThumbGenerator import com.hippo.ehviewer.util.FavouriteStatusRouter @@ -219,22 +221,25 @@ fun GalleryInfoGridItem( interactionSource = interactionSource, ) { Box { - var ratio by remember(info) { - val ratio = if (info.thumbHeight != 0) { - (info.thumbWidth.toFloat() / info.thumbHeight).coerceIn(MIN_RATIO, MAX_RATIO) - } else { - DEFAULT_RATIO - } - mutableFloatStateOf(ratio) - } with(listThumbGenerator) { - EhAsyncThumb( - model = info, - modifier = Modifier.aspectRatio(ratio), - onSuccess = { - ratio = (it.width.toFloat() / it.height).coerceIn(MIN_RATIO, MAX_RATIO) - }, - ) + SharedElementBox(key = "${info.gid}", shape = ShapeDefaults.Medium) { + var ratio by remember(info) { + val ratio = if (info.thumbHeight != 0) { + (info.thumbWidth.toFloat() / info.thumbHeight).coerceIn(MIN_RATIO, MAX_RATIO) + } else { + DEFAULT_RATIO + } + mutableFloatStateOf(ratio) + } + AsyncImage( + model = requestOf(info), + contentDescription = null, + modifier = Modifier.aspectRatio(ratio), + onSuccess = { + ratio = (it.result.image.width.toFloat() / it.result.image.height).coerceIn(MIN_RATIO, MAX_RATIO) + }, + ) + } } val categoryColor = EhUtils.getCategoryColor(info.category) Badge( diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SharedElementTransition.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SharedElementTransition.kt index 75fdcf138..2313d6566 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SharedElementTransition.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SharedElementTransition.kt @@ -6,11 +6,16 @@ import androidx.compose.animation.ExitTransition import androidx.compose.animation.SharedTransitionScope import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Shape import java.util.concurrent.atomic.AtomicInteger val NoopTransitionsVisibilityScope = TransitionsVisibilityScope(emptySet()) @@ -45,6 +50,13 @@ fun Modifier.sharedBounds( } } +context(SharedTransitionScope, TransitionsVisibilityScope, SETNodeGenerator) +@Composable +inline fun SharedElementBox(key: String, shape: Shape, crossinline content: @Composable BoxScope.() -> Unit) { + val modifier = Modifier.sharedBounds(key = key).clip(shape) + CompositionLocalProvider { Box(modifier = modifier, content = content) } +} + data class SETNode( val contentKey: String, val uniqueID: String,