[New Reader] Implement custom image scale types
This commit is contained in:
1 parent
4b8ecbf6a7
commit
7b1f94cf5a
3 files changed
+164
-114
No files matched your search
@@ -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<ViewerNavigation>,
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user