[Reader] WIP

This commit is contained in:
tom5079
2021-12-24 17:10:22 +09:00
parent 7e52a2e296
commit f78c66a9f4
4 changed files with 433 additions and 253 deletions

View File

@@ -41,5 +41,10 @@
<option name="name" value="MavenLocal" /> <option name="name" value="MavenLocal" />
<option name="url" value="file:/$USER_HOME$/.m2/repository" /> <option name="url" value="file:/$USER_HOME$/.m2/repository" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
</remote-repository>
</component> </component>
</project> </project>

View File

@@ -137,7 +137,7 @@ dependencies {
implementation("ru.noties.markwon:core:3.1.0") implementation("ru.noties.markwon:core:3.1.0")
implementation("xyz.quaver:documentfilex:0.7.1") implementation("xyz.quaver:documentfilex:0.7.1")
implementation("xyz.quaver:subsampledimage:0.0.1-alpha13-SNAPSHOT") implementation("xyz.quaver:subsampledimage:0.0.1-alpha14-SNAPSHOT-DEV01")
implementation("com.google.guava:guava:31.0.1-jre") implementation("com.google.guava:guava:31.0.1-jre")

View File

@@ -25,8 +25,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.*
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AutoFixHigh import androidx.compose.material.icons.filled.AutoFixHigh
@@ -39,15 +38,20 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
@@ -78,6 +82,7 @@ import xyz.quaver.pupil.db.AppDatabase
import xyz.quaver.pupil.proto.ReaderOptions import xyz.quaver.pupil.proto.ReaderOptions
import xyz.quaver.pupil.proto.settingsDataStore import xyz.quaver.pupil.proto.settingsDataStore
import xyz.quaver.pupil.ui.theme.Orange500 import xyz.quaver.pupil.ui.theme.Orange500
import xyz.quaver.pupil.util.FileXImageSource
import xyz.quaver.pupil.util.NetworkCache import xyz.quaver.pupil.util.NetworkCache
import xyz.quaver.pupil.util.activity import xyz.quaver.pupil.util.activity
import xyz.quaver.pupil.util.rememberFileXImageSource import xyz.quaver.pupil.util.rememberFileXImageSource
@@ -248,12 +253,356 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar
} }
} }
val ReaderOptions.Orientation.isVertical: Boolean
get() =
this == ReaderOptions.Orientation.VERTICAL_DOWN ||
this == ReaderOptions.Orientation.VERTICAL_UP
val ReaderOptions.Orientation.isReverse: Boolean
get() =
this == ReaderOptions.Orientation.VERTICAL_UP ||
this == ReaderOptions.Orientation.HORIZONTAL_LEFT
@Composable
fun ReaderOptionsSheet(readerOptions: ReaderOptions, onOptionsChange: (ReaderOptions.Builder.() -> Unit) -> Unit) {
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.h6) {
Column(Modifier.padding(16.dp, 0.dp)) {
val layout = readerOptions.layout
val snap = readerOptions.snap
val orientation = readerOptions.orientation
val padding = readerOptions.padding
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Layout")
Row {
listOf(
ReaderOptions.Layout.SINGLE_PAGE to SingleImage,
ReaderOptions.Layout.DOUBLE_PAGE to DoubleImage,
ReaderOptions.Layout.AUTO to Icons.Default.AutoFixHigh
).forEach { (option, icon) ->
IconButton(onClick = {
onOptionsChange {
setLayout(option)
}
}) {
Icon(
icon,
contentDescription = null,
tint =
if (layout == option) MaterialTheme.colors.secondary
else LocalContentColor.current
)
}
}
}
}
val infiniteTransition = rememberInfiniteTransition()
val isReverse = orientation.isReverse
val isVertical = orientation.isVertical
val animationOrientation = if (isReverse) -1f else 1f
val animationSpacing by animateFloatAsState(if (padding) 48f else 32f)
val animationOffset by infiniteTransition.animateFloat(
initialValue = animationOrientation * (if (snap) 0f else animationSpacing/2),
targetValue = animationOrientation * (if (snap) -animationSpacing else -animationSpacing/2),
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = if(snap) FastOutSlowInEasing else LinearEasing
),
repeatMode = RepeatMode.Restart
)
)
val animationRotation by animateFloatAsState(if (isVertical) 90f else 0f)
val setOrientation: (Boolean, Boolean) -> Unit = { isVertical, isReverse ->
val orientation = when {
isVertical && !isReverse -> ReaderOptions.Orientation.VERTICAL_DOWN
isVertical && isReverse -> ReaderOptions.Orientation.VERTICAL_UP
!isVertical && !isReverse -> ReaderOptions.Orientation.HORIZONTAL_RIGHT
!isVertical && isReverse -> ReaderOptions.Orientation.HORIZONTAL_LEFT
else -> error("Invalid value")
}
onOptionsChange {
setOrientation(orientation)
}
}
Box(
modifier = Modifier
.size(48.dp)
.clipToBounds()
.rotate(animationRotation)
.align(Alignment.CenterHorizontally)
) {
for (i in 0..4)
Icon(
SingleImage,
contentDescription = null,
modifier = Modifier
.size(48.dp)
.align(Alignment.CenterStart)
.offset((animationOffset + animationSpacing * (i - 2)).dp, 0.dp)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Orientation")
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.caption) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("H")
Switch(checked = isVertical, onCheckedChange = {
setOrientation(!isVertical, isReverse)
})
Text("V")
}
}
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Reverse")
Switch(checked = isReverse, onCheckedChange = {
setOrientation(isVertical, !isReverse)
})
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Snap")
Switch(checked = snap, onCheckedChange = {
onOptionsChange {
setSnap(!snap)
}
})
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Padding")
Switch(checked = padding, onCheckedChange = {
onOptionsChange {
setPadding(!padding)
}
})
}
Box(
Modifier
.fillMaxWidth()
.height(8.dp))
}
}
}
@Composable
fun BoxScope.ReaderLazyList(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
orientation: ReaderOptions.Orientation,
onScroll: (direction: Float) -> Unit,
content: LazyListScope.() -> Unit
) {
val isReverse = orientation.isReverse
val nestedScrollConnection = remember(orientation) { object: NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
onScroll(
when (orientation) {
ReaderOptions.Orientation.VERTICAL_DOWN -> available.y.sign
ReaderOptions.Orientation.VERTICAL_UP -> -(available.y.sign)
ReaderOptions.Orientation.HORIZONTAL_RIGHT -> available.x.sign
ReaderOptions.Orientation.HORIZONTAL_LEFT -> -(available.x.sign)
}
)
return Offset.Zero
}
} }
when (orientation) {
ReaderOptions.Orientation.VERTICAL_DOWN,
ReaderOptions.Orientation.VERTICAL_UP ->
LazyColumn(
modifier = modifier
.fillMaxSize()
.align(Alignment.TopStart)
.nestedScroll(nestedScrollConnection),
state = state,
verticalArrangement = Arrangement.spacedBy(4.dp),
contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars),
reverseLayout = isReverse,
content = content
)
ReaderOptions.Orientation.HORIZONTAL_RIGHT,
ReaderOptions.Orientation.HORIZONTAL_LEFT ->
LazyRow(
modifier = modifier
.fillMaxSize()
.align(Alignment.CenterStart)
.nestedScroll(nestedScrollConnection),
state = state,
horizontalArrangement = Arrangement.spacedBy(4.dp),
reverseLayout = isReverse,
content = content
)
}
}
@Composable
fun ReaderLayoutItem(
modifier: Modifier = Modifier,
isVertical: Boolean,
content: @Composable (Modifier) -> Unit
) {
if (isVertical)
Row(
modifier = Modifier.fillMaxWidth()
) {
content(modifier.weight(1f))
}
else
Column(
modifier = Modifier.fillMaxHeight()
) {
content(modifier.weight(1f))
}
}
@ExperimentalFoundationApi
@Composable
fun ReaderItem(
model: ReaderBaseViewModel,
readerOptions: ReaderOptions,
listSize: Size,
imageSources: List<ImageSource?>,
) {
val context = LocalContext.current
val state = rememberSubSampledImageState(
when {
readerOptions.padding -> ScaleTypes.CENTER_INSIDE
readerOptions.orientation.isVertical -> ScaleTypes.FIT_WIDTH
else -> ScaleTypes.FIT_HEIGHT
}
)
val listSizeDp = LocalDensity.current.run { listSize.width.toDp() to listSize.height.toDp() }
val modifier = when {
readerOptions.padding -> Modifier.size(listSizeDp.first, listSizeDp.second)
readerOptions.orientation.isVertical -> Modifier
.wrapContentHeight(state, listSizeDp.second)
.fillMaxWidth()
else -> Modifier
.wrapContentWidth(state, listSizeDp.first)
.fillMaxHeight()
}
ReaderLayoutItem(modifier, readerOptions.orientation.isVertical) { modifier ->
indices.forEach { index ->
Box(
modifier.border(1.dp, Color.Gray),
contentAlignment = Alignment.Center
) {
val progress = model.progressList.getOrNull(index) ?: 0f
val uri = model.imageList.getOrNull(index)
if (progress == Float.NEGATIVE_INFINITY)
Icon(Icons.Filled.BrokenImage, null, tint = Orange500)
else if (progress.isFinite())
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
LinearProgressIndicator(progress)
Text((index + 1).toString())
}
else if (uri != null && progress == Float.POSITIVE_INFINITY) {
val imageSource = kotlin.runCatching {
rememberFileXImageSource(FileX(context, uri))
}.getOrNull()
if (imageSource != null)
SubSampledImage(
modifier = Modifier
.fillMaxSize()
.run {
if (model.fullscreen)
doubleClickCycleZoom(state, 2f)
else
combinedClickable(
onLongClick = {
}
) {
model.fullscreen = true
}
},
imageSource = imageSource,
state = state,
onError = {
model.error(index)
}
)
}
}
}
}
}
@ExperimentalFoundationApi
fun LazyListScope.ReaderLazyListContent(
model: ReaderBaseViewModel,
listSize: Size,
imageSources: List<ImageSource?>,
imageSizes: List<Size?>,
readerOptions: ReaderOptions
) {
when {
readerOptions.layout == ReaderOptions.Layout.SINGLE_PAGE ->
items(imageSources) { source ->
ReaderItem(model, readerOptions, listSize, listOf(source))
}
readerOptions.layout == ReaderOptions.Layout.DOUBLE_PAGE ->
items(imageSources.size/2 + (imageSources.size and 0x1)) { i ->
ReaderItem(model, readerOptions, listSize, imageSources.subList(2*i, 2*i+2))
}
else ->
items(imageSources) { source ->
ReaderItem(model, readerOptions, listSize, listOf(source))
}
}
}
@ExperimentalMaterialApi @ExperimentalMaterialApi
@ExperimentalFoundationApi @ExperimentalFoundationApi
@Composable @Composable
fun ReaderBase( fun ReaderBase(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
model: ReaderBaseViewModel model: ReaderBaseViewModel,
onScroll: (direction: Float) -> Unit = { }
) { ) {
val context = LocalContext.current val context = LocalContext.current
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
@@ -265,18 +614,14 @@ fun ReaderBase(
var scrollDirection by remember { mutableStateOf(0f) } var scrollDirection by remember { mutableStateOf(0f) }
val handleOffset by animateDpAsState(if (model.fullscreen || scrollDirection < 0f) (-36).dp else 0.dp) val handleOffset by animateDpAsState(if (model.fullscreen || scrollDirection < 0f) (-36).dp else 0.dp)
val mainReaderOptions by remember { val mainReaderOptions by remember {
context.settingsDataStore.data.map { it.mainReaderOption } context.settingsDataStore.data.map { it.mainReaderOption }
}.collectAsState(ReaderOptions.getDefaultInstance()) }.collectAsState(ReaderOptions.getDefaultInstance())
val nestedScrollConnection = remember { object: NestedScrollConnection { LaunchedEffect(scrollDirection) {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { onScroll(scrollDirection)
scrollDirection = available.y.sign }
return Offset.Zero
}
} }
LaunchedEffect(model.fullscreen) { LaunchedEffect(model.fullscreen) {
context.activity?.window?.let { window -> context.activity?.window?.let { window ->
@@ -305,249 +650,85 @@ fun ReaderBase(
ModalTopSheetLayout( ModalTopSheetLayout(
modifier = Modifier.offset(0.dp, handleOffset), modifier = Modifier.offset(0.dp, handleOffset),
drawerContent = { drawerContent = {
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.h6) { ReaderOptionsSheet(mainReaderOptions) { readerOptionsBlock ->
Column(Modifier.padding(16.dp, 0.dp)) { coroutineScope.launch {
val layout = mainReaderOptions.layout context.settingsDataStore.updateData {
val snap = mainReaderOptions.snap it.toBuilder().setMainReaderOption(
val orientation = mainReaderOptions.orientation mainReaderOptions.toBuilder().apply(readerOptionsBlock).build()
val padding = mainReaderOptions.padding ).build()
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Layout")
Row {
listOf(
ReaderOptions.Layout.SINGLE_PAGE to SingleImage,
ReaderOptions.Layout.DOUBLE_PAGE to DoubleImage,
ReaderOptions.Layout.AUTO to Icons.Default.AutoFixHigh
).forEach { (option, icon) ->
IconButton(onClick = {
coroutineScope.launch {
context.settingsDataStore.updateData {
it.toBuilder().setMainReaderOption(
it.mainReaderOption.toBuilder()
.setLayout(option)
.build()
).build()
}
}
}) {
Icon(
icon,
contentDescription = null,
tint =
if (layout == option) MaterialTheme.colors.secondary
else LocalContentColor.current
)
}
}
}
} }
val infiniteTransition = rememberInfiniteTransition()
val isVertical =
orientation == ReaderOptions.Orientation.VERTICAL_DOWN ||
orientation == ReaderOptions.Orientation.VERTICAL_UP
val isReverse =
orientation == ReaderOptions.Orientation.VERTICAL_UP ||
orientation == ReaderOptions.Orientation.HORIZONTAL_LEFT
val animationOrientation = if (isReverse) -1f else 1f
val animationSpacing by animateFloatAsState(if (padding) 48f else 32f)
val animationOffset by infiniteTransition.animateFloat(
initialValue = animationOrientation * (if (snap) 0f else animationSpacing/2),
targetValue = animationOrientation * (if (snap) -animationSpacing else -animationSpacing/2),
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = if(snap) FastOutSlowInEasing else LinearEasing
),
repeatMode = RepeatMode.Restart
)
)
val animationRotation by animateFloatAsState(if (isVertical) 90f else 0f)
val setOrientation: (Boolean, Boolean) -> Unit = { isVertical, isReverse ->
val orientation = when {
isVertical && !isReverse -> ReaderOptions.Orientation.VERTICAL_DOWN
isVertical && isReverse -> ReaderOptions.Orientation.VERTICAL_UP
!isVertical && !isReverse -> ReaderOptions.Orientation.HORIZONTAL_RIGHT
!isVertical && isReverse -> ReaderOptions.Orientation.HORIZONTAL_LEFT
else -> error("Invalid value")
}
coroutineScope.launch {
context.settingsDataStore.updateData {
it.toBuilder().setMainReaderOption(
mainReaderOptions.toBuilder()
.setOrientation(orientation)
.build()
).build()
}
}
}
Box(
modifier = Modifier
.size(48.dp)
.clipToBounds()
.rotate(animationRotation)
.align(Alignment.CenterHorizontally)
) {
for (i in 0..4)
Icon(
SingleImage,
contentDescription = null,
modifier = Modifier
.size(48.dp)
.align(Alignment.CenterStart)
.offset((animationOffset + animationSpacing * (i - 2)).dp, 0.dp)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Orientation")
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.caption) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("H")
Switch(checked = isVertical, onCheckedChange = {
setOrientation(!isVertical, isReverse)
})
Text("V")
}
}
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Reverse")
Switch(checked = isReverse, onCheckedChange = {
setOrientation(isVertical, !isReverse)
})
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Snap")
Switch(checked = snap, onCheckedChange = {
coroutineScope.launch {
context.settingsDataStore.updateData {
it.toBuilder().setMainReaderOption(
mainReaderOptions.toBuilder()
.setSnap(!snap)
.build()
).build()
}
}
})
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Padding")
Switch(checked = padding, onCheckedChange = {
coroutineScope.launch {
context.settingsDataStore.updateData {
it.toBuilder().setMainReaderOption(
mainReaderOptions.toBuilder()
.setPadding(!padding)
.build()
).build()
}
}
})
}
Box(
Modifier
.fillMaxWidth()
.height(8.dp))
} }
} }
} }
) { ) {
LazyColumn( var listSize: Size? by remember { mutableStateOf(null) }
Modifier val listState = rememberLazyListState()
.fillMaxSize()
.align(Alignment.TopStart)
.nestedScroll(nestedScrollConnection),
verticalArrangement = Arrangement.spacedBy(4.dp),
contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars)
) {
itemsIndexed(model.imageList) { i, uri ->
val state = rememberSubSampledImageState(ScaleTypes.FIT_WIDTH)
Box( val nestedScrollConnection = remember { object: NestedScrollConnection {
Modifier override suspend fun onPreFling(available: Velocity): Velocity {
.wrapContentHeight(state, 500.dp) return if (mainReaderOptions.snap) {
.fillMaxWidth() val velocity = when (mainReaderOptions.orientation) {
.border(1.dp, Color.Gray), ReaderOptions.Orientation.VERTICAL_DOWN -> available.y
contentAlignment = Alignment.Center ReaderOptions.Orientation.VERTICAL_UP -> -(available.y)
) { ReaderOptions.Orientation.HORIZONTAL_RIGHT -> available.x
val progress = model.progressList.getOrNull(i) ?: 0f ReaderOptions.Orientation.HORIZONTAL_LEFT -> -(available.x)
if (progress == Float.NEGATIVE_INFINITY)
Icon(Icons.Filled.BrokenImage, null, tint = Orange500)
else if (progress.isFinite())
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
LinearProgressIndicator(progress)
Text((i + 1).toString())
}
else if (uri != null && progress == Float.POSITIVE_INFINITY) {
val imageSource = kotlin.runCatching {
rememberFileXImageSource(FileX(context, uri))
}.getOrNull()
if (imageSource != null)
SubSampledImage(
modifier = Modifier
.fillMaxSize()
.run {
if (model.fullscreen)
doubleClickCycleZoom(state, 2f)
else
combinedClickable(
onLongClick = {
}
) {
model.fullscreen = true
}
},
imageSource = imageSource,
state = state,
onError = {
model.error(i)
}
)
} }
val index = listState.firstVisibleItemIndex
coroutineScope.launch {
when {
velocity < 0f -> listState.animateScrollToItem(index+1)
else -> listState.animateScrollToItem(index)
}
}
available
} else Velocity.Zero
}
} }
val imageSources = remember { mutableStateListOf<ImageSource?>() }
val imageSizes = remember { mutableStateListOf<Size?>() }
LaunchedEffect(model.imageList.count { it != null }) {
if (imageSources.size != model.imageList.size)
imageSources.addAll(List (model.imageList.size-imageSources.size) { null })
if (imageSizes.size != model.imageList.size)
imageSizes.addAll(List (model.imageList.size-imageSources.size) { null })
coroutineScope.launch {
model.imageList.forEachIndexed { i, uri ->
if (imageSources[i] == null && uri != null)
imageSources[i] = FileXImageSource(FileX(context, uri))
if (imageSizes[i] == null)
imageSources[i]?.let {
imageSizes[i] = it.imageSize
}
} }
} }
} }
ReaderLazyList(
Modifier
.onGloballyPositioned { listSize = it.size.toSize() }
.nestedScroll(nestedScrollConnection),
listState,
mainReaderOptions.orientation,
onScroll = { scrollDirection = it },
) {
ReaderLazyListContent(
model,
listSize ?: Size.Zero,
imageSources,
imageSizes,
mainReaderOptions
)
}
if (model.progressList.any { it.isFinite() }) if (model.progressList.any { it.isFinite() })
LinearProgressIndicator( LinearProgressIndicator(
modifier = Modifier modifier = Modifier

View File

@@ -415,13 +415,6 @@ class Manatoki(app: Application) : Source(), DIAware {
val listState = rememberLazyListState() val listState = rememberLazyListState()
var scrollDirection by remember { mutableStateOf(0f) } var scrollDirection by remember { mutableStateOf(0f) }
val nestedScrollConnection = remember { object: NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
scrollDirection = available.y.sign
return Offset.Zero
}
} }
BackHandler { BackHandler {
when { when {
@@ -572,8 +565,9 @@ class Manatoki(app: Application) : Source(), DIAware {
} }
) { contentPadding -> ) { contentPadding ->
ReaderBase( ReaderBase(
Modifier.padding(contentPadding).nestedScroll(nestedScrollConnection), Modifier.padding(contentPadding),
model model = model,
onScroll = { scrollDirection = it }
) )
} }
} }