diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/GalleryPager.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/GalleryPager.kt index 36397f5b7..b5dcb3487 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/GalleryPager.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/GalleryPager.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -25,8 +26,13 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.FixedScale +import androidx.compose.ui.layout.times import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.toSize import com.hippo.ehviewer.Settings import com.hippo.ehviewer.collectAsState import com.hippo.ehviewer.gallery.PageLoader2 @@ -47,6 +53,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import me.saket.telephoto.zoomable.DoubleClickToZoomListener import me.saket.telephoto.zoomable.ZoomSpec +import me.saket.telephoto.zoomable.ZoomableContentLocation import me.saket.telephoto.zoomable.ZoomableState import me.saket.telephoto.zoomable.rememberZoomableState import me.saket.telephoto.zoomable.zoomable @@ -65,9 +72,14 @@ fun GalleryPager( val scope = rememberCoroutineScope() val items = pageLoader.pages when (type) { - DEFAULT, LEFT_TO_RIGHT, RIGHT_TO_LEFT -> { + DEFAULT, LEFT_TO_RIGHT, RIGHT_TO_LEFT, VERTICAL -> { + val isVertical = type == VERTICAL + val scaleType by Settings.imageScaleType.collectAsState() + val alignment by Settings.zoomStart.collectAsState { + Alignment.fromPreferences(it, isVertical) + } val navigatorState = Settings.readerPagerNav.collectAsState { - ViewerNavigation.fromPreference(it, false) + ViewerNavigation.fromPreference(it, isVertical) } val invertMode by Settings.readerPagerNavInverted.collectAsState { TappingInvertMode.entries[it] @@ -75,54 +87,57 @@ fun GalleryPager( LaunchedEffect(navigatorState.value, invertMode) { navigatorState.value.invertMode = invertMode } - val isRtl = type == RIGHT_TO_LEFT - HorizontalPager( - state = pagerState, - modifier = modifier, - contentPadding = contentPadding, - reverseLayout = isRtl, - key = { it }, - ) { index -> - val page = items[index] - PageContainer( - page = page, - pageLoader = pageLoader, - isRtl = isRtl, - navigatorState = navigatorState, - pagerState = pagerState, - onSelectPage = onSelectPage, - onMenuRegionClick = onMenuRegionClick, - scope = scope, - ) + val layoutSize by remember(pagerState) { + derivedStateOf { + pagerState.layoutInfo.viewportSize.toSize() + } } - } - VERTICAL -> { - val navigatorState = Settings.readerPagerNav.collectAsState { - ViewerNavigation.fromPreference(it, true) - } - val invertMode by Settings.readerPagerNavInverted.collectAsState { - TappingInvertMode.entries[it] - } - LaunchedEffect(navigatorState.value, invertMode) { - navigatorState.value.invertMode = invertMode - } - VerticalPager( - state = pagerState, - modifier = modifier, - contentPadding = contentPadding, - key = { it }, - ) { index -> - val page = items[index] - PageContainer( - page = page, - pageLoader = pageLoader, - isRtl = false, - navigatorState = navigatorState, - pagerState = pagerState, - onSelectPage = onSelectPage, - onMenuRegionClick = onMenuRegionClick, - scope = scope, - ) + if (isVertical) { + VerticalPager( + state = pagerState, + modifier = modifier, + contentPadding = contentPadding, + key = { it }, + ) { index -> + val page = items[index] + PageContainer( + page = page, + pageLoader = pageLoader, + isRtl = false, + scaleType = scaleType, + alignment = alignment, + layoutSize = layoutSize, + navigatorState = navigatorState, + pagerState = pagerState, + onSelectPage = onSelectPage, + onMenuRegionClick = onMenuRegionClick, + scope = scope, + ) + } + } else { + val isRtl = type == RIGHT_TO_LEFT + HorizontalPager( + state = pagerState, + modifier = modifier, + contentPadding = contentPadding, + reverseLayout = isRtl, + key = { it }, + ) { index -> + val page = items[index] + PageContainer( + page = page, + pageLoader = pageLoader, + isRtl = isRtl, + scaleType = scaleType, + alignment = alignment, + layoutSize = layoutSize, + navigatorState = navigatorState, + pagerState = pagerState, + onSelectPage = onSelectPage, + onMenuRegionClick = onMenuRegionClick, + scope = scope, + ) + } } } WEBTOON, CONTINUOUS_VERTICAL -> { @@ -183,8 +198,7 @@ fun GalleryPager( PagerItem( page = page, pageLoader = pageLoader, - zoomableState = zoomableState, - webtoon = true, + contentScale = ContentScale.FillWidth, ) } } @@ -197,6 +211,9 @@ private fun PageContainer( page: ReaderPage, pageLoader: PageLoader2, isRtl: Boolean, + scaleType: Int, + alignment: Alignment.Horizontal, + layoutSize: Size, navigatorState: State, pagerState: PagerState, onSelectPage: (ReaderPage) -> Unit, @@ -207,59 +224,81 @@ private fun PageContainer( val isRtl by rememberUpdatedState(isRtl) val zoomableState = rememberZoomableState(zoomSpec = zoomSpec) val status by page.status.collectAsState() - Box( - modifier = Modifier.zoomable( - state = zoomableState, - enabled = status == Page.State.READY, - onClick = { - scope.launch { - with(pagerState) { - with(zoomableState) { - val size = layoutInfo.viewportSize - val pos = PointF(it.x / size.width, it.y / size.height) - val distance = size.width.toFloat() - when (navigatorState.value.getAction(pos)) { - NavigationRegion.MENU -> onMenuRegionClick() - NavigationRegion.NEXT -> { - val canPan = if (isRtl) panRight(distance) else panLeft(distance) - if (!canPan) { - moveToNext() + if (status == Page.State.READY) { + val size = page.image!!.rect.size.toSize() + val contentScale = ContentScale.fromPreferences(scaleType, size, layoutSize) + zoomableState.contentScale = contentScale + LaunchedEffect(size, contentScale, alignment) { + val contentSize = if (contentScale is FixedScale) { // Original + size + } else { + size * contentScale.computeScaleFactor(size, layoutSize) + } + val horizontalAlignment = if (contentSize.width > layoutSize.width) { + alignment + } else { + Alignment.CenterHorizontally + } + val verticalAlignment = if (contentSize.height > layoutSize.height) { + Alignment.Top + } else { + Alignment.CenterVertically + } + zoomableState.contentAlignment = horizontalAlignment + verticalAlignment + } + LaunchedEffect(size) { + val contentLocation = ZoomableContentLocation.scaledInsideAndCenterAligned(size) + zoomableState.setContentLocation(contentLocation) + } + } + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + PagerItem( + page = page, + pageLoader = pageLoader, + contentScale = ContentScale.Inside, + modifier = Modifier.zoomable( + state = zoomableState, + onClick = { + scope.launch { + with(pagerState) { + with(zoomableState) { + val pos = PointF(it.x / layoutSize.width, it.y / layoutSize.height) + val distance = layoutSize.width + when (navigatorState.value.getAction(pos)) { + NavigationRegion.MENU -> onMenuRegionClick() + NavigationRegion.NEXT -> { + val canPan = if (isRtl) panRight(distance) else panLeft(distance) + if (!canPan) { + moveToNext() + } } - } - NavigationRegion.PREV -> { - val canPan = if (isRtl) panLeft(distance) else panRight(distance) - if (!canPan) { - moveToPrevious() + NavigationRegion.PREV -> { + val canPan = if (isRtl) panLeft(distance) else panRight(distance) + if (!canPan) { + moveToPrevious() + } } - } - NavigationRegion.RIGHT -> { - if (!panLeft(distance)) { - if (isRtl) moveToPrevious() else moveToNext() + NavigationRegion.RIGHT -> { + if (!panLeft(distance)) { + if (isRtl) moveToPrevious() else moveToNext() + } } - } - NavigationRegion.LEFT -> { - if (!panRight(distance)) { - if (isRtl) moveToNext() else moveToPrevious() + NavigationRegion.LEFT -> { + if (!panRight(distance)) { + if (isRtl) moveToNext() else moveToPrevious() + } } } } } } - } - }, - onLongClick = { onSelectPage(page) }, - onDoubleClick = DoubleTapZoom, - ).fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - PagerItem( - page = page, - pageLoader = pageLoader, - zoomableState = zoomableState, - webtoon = false, + }, + onLongClick = { onSelectPage(page) }, + onDoubleClick = DoubleTapZoom, + ), ) } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/PagerItem.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/PagerItem.kt index e1c3d3d34..87ab35592 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/PagerItem.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/PagerItem.kt @@ -24,29 +24,26 @@ import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import coil3.BitmapImage import coil3.DrawableImage -import coil3.Image import com.google.accompanist.drawablepainter.DrawablePainter import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.collectAsState +import com.hippo.ehviewer.image.Image import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.loader.PageLoader import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.viewer.CombinedCircularProgressIndicator -import me.saket.telephoto.zoomable.ZoomableContentLocation -import me.saket.telephoto.zoomable.ZoomableState import moe.tarsin.kt.unreachable @Composable fun PagerItem( page: ReaderPage, pageLoader: PageLoader, - zoomableState: ZoomableState, - webtoon: Boolean, + contentScale: ContentScale, + modifier: Modifier = Modifier, ) { val defaultError = stringResource(id = R.string.decode_image_error) val state by page.status.collectAsState() @@ -64,16 +61,14 @@ fun PagerItem( } } Page.State.READY -> { - val image = page.image!!.innerImage!! - val rect = page.image!!.rect - val painter = remember(image) { image.toPainter(rect) } - val contentScale = if (webtoon) ContentScale.FillWidth else ContentScale.Inside + val image = page.image!! + val painter = remember(image) { image.toPainter() } val grayScale by Settings.grayScale.collectAsState() val invert by Settings.invertedColors.collectAsState() Image( painter = painter, contentDescription = null, - modifier = Modifier.fillMaxSize(), + modifier = modifier.fillMaxSize(), contentScale = contentScale, colorFilter = when { grayScale && invert -> grayScaleAndInvertFilter @@ -82,13 +77,6 @@ fun PagerItem( else -> null }, ) - if (!webtoon) { - val size = painter.intrinsicSize - LaunchedEffect(size) { - val contentLocation = ZoomableContentLocation.scaledInsideAndCenterAligned(size) - zoomableState.setContentLocation(contentLocation) - } - } } Page.State.ERROR -> { Box(modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT)) { @@ -111,13 +99,13 @@ fun PagerItem( } } -private fun Image.toPainter(rect: IntRect) = when (this) { +private fun Image.toPainter() = when (val image = innerImage) { is BitmapImage -> BitmapPainter( - image = bitmap.asImageBitmap(), + image = image.bitmap.asImageBitmap(), srcOffset = rect.topLeft, srcSize = rect.size, ) - is DrawableImage -> DrawablePainter(drawable) + is DrawableImage -> DrawablePainter(image.drawable) else -> unreachable() } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/Utils.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/Utils.kt index 0c9b8b989..6ba8e3bb9 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/Utils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/Utils.kt @@ -5,6 +5,12 @@ import android.view.Window import android.view.WindowManager import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Stable +import androidx.compose.ui.AbsoluteAlignment +import androidx.compose.ui.Alignment +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.FixedScale import com.hippo.ehviewer.Settings import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import kotlinx.coroutines.flow.onCompletion @@ -51,3 +57,20 @@ fun Activity.setCustomBrightnessValue(value: Int) { window.attributes = window.attributes.apply { screenBrightness = readerBrightness } } + +fun Alignment.Companion.fromPreferences(value: Int, isVertical: Boolean) = when (value) { + 1 -> if (isVertical) CenterHorizontally else Start + 2 -> AbsoluteAlignment.Left + 3 -> AbsoluteAlignment.Right + else -> CenterHorizontally +} + +@Stable +fun ContentScale.Companion.fromPreferences(value: Int, srcSize: Size, dstSize: Size) = when (value) { + 2 -> Crop + 3 -> FillWidth + 4 -> FillHeight + 5 -> FixedScale(1 / Inside.computeScaleFactor(srcSize, dstSize).scaleX) + 6 -> if (srcSize.width > srcSize.height) FillHeight else FillWidth + else -> Fit +}