[Reader] ReaderOptions TopSheet
This commit is contained in:
@@ -45,7 +45,7 @@ import xyz.quaver.pupil.sources.composable.ModalTopSheetState.Expanded
|
||||
import xyz.quaver.pupil.sources.composable.ModalTopSheetState.Hidden
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ModalTopSheetLayout(
|
||||
class ModalTopSheetLayoutShape(
|
||||
private val cornerRadius: Dp,
|
||||
private val handleRadius: Dp
|
||||
): Shape {
|
||||
@@ -152,7 +152,7 @@ private fun Scrim(
|
||||
|
||||
@Composable
|
||||
@ExperimentalMaterialApi
|
||||
fun SearchOptionDrawer(
|
||||
fun ModalTopSheetLayout(
|
||||
drawerContent: @Composable ColumnScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
drawerCornerRadius: Dp = SearchOptionDrawerDefaults.CornerRadius,
|
||||
@@ -229,7 +229,7 @@ fun SearchOptionDrawer(
|
||||
.onGloballyPositioned {
|
||||
sheetHeight = it.size.height.toFloat()
|
||||
},
|
||||
shape = ModalTopSheetLayout(drawerCornerRadius, drawerHandleRadius),
|
||||
shape = ModalTopSheetLayoutShape(drawerCornerRadius, drawerHandleRadius),
|
||||
elevation = drawerElevation,
|
||||
color = drawerBackgroundColor,
|
||||
contentColor = drawerContentColor
|
||||
|
||||
@@ -20,6 +20,7 @@ package xyz.quaver.pupil.sources.composable
|
||||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
@@ -28,11 +29,21 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AutoFixHigh
|
||||
import androidx.compose.material.icons.filled.BrokenImage
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -50,6 +61,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -63,12 +75,93 @@ import xyz.quaver.graphics.subsampledimage.*
|
||||
import xyz.quaver.io.FileX
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.db.AppDatabase
|
||||
import xyz.quaver.pupil.proto.ReaderOptions
|
||||
import xyz.quaver.pupil.proto.settingsDataStore
|
||||
import xyz.quaver.pupil.ui.theme.Orange500
|
||||
import xyz.quaver.pupil.util.NetworkCache
|
||||
import xyz.quaver.pupil.util.activity
|
||||
import xyz.quaver.pupil.util.rememberFileXImageSource
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sign
|
||||
|
||||
private var _singleImage: ImageVector? = null
|
||||
val SingleImage: ImageVector
|
||||
get() {
|
||||
if (_singleImage != null) {
|
||||
return _singleImage!!
|
||||
}
|
||||
|
||||
_singleImage = materialIcon(name = "ReaderBase.SingleImage") {
|
||||
materialPath {
|
||||
moveTo(17.0f, 3.0f)
|
||||
lineTo(7.0f, 3.0f)
|
||||
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
|
||||
verticalLineToRelative(14.0f)
|
||||
curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f)
|
||||
horizontalLineToRelative(10.0f)
|
||||
curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f)
|
||||
lineTo(19.0f, 5.0f)
|
||||
curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f)
|
||||
close()
|
||||
moveTo(17.0f, 19.0f)
|
||||
lineTo(7.0f, 19.0f)
|
||||
lineTo(7.0f, 5.0f)
|
||||
horizontalLineToRelative(10.0f)
|
||||
verticalLineToRelative(14.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
return _singleImage!!
|
||||
}
|
||||
|
||||
private var _doubleImage: ImageVector? = null
|
||||
val DoubleImage: ImageVector
|
||||
get() {
|
||||
if (_doubleImage != null) {
|
||||
return _doubleImage!!
|
||||
}
|
||||
|
||||
_doubleImage = materialIcon(name = "ReaderBase.DoubleImage") {
|
||||
materialPath {
|
||||
moveTo(9.0f, 3.0f)
|
||||
lineTo(2.0f, 3.0f)
|
||||
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
|
||||
verticalLineToRelative(14.0f)
|
||||
curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f)
|
||||
horizontalLineToRelative(7.0f)
|
||||
curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f)
|
||||
lineTo(11.0f, 5.0f)
|
||||
curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f)
|
||||
close()
|
||||
moveTo(9.0f, 19.0f)
|
||||
lineTo(2.0f, 19.0f)
|
||||
lineTo(2.0f, 5.0f)
|
||||
horizontalLineToRelative(7.0f)
|
||||
verticalLineToRelative(14.0f)
|
||||
close()
|
||||
moveTo(21.0f, 3.0f)
|
||||
lineTo(14.0f, 3.0f)
|
||||
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
|
||||
verticalLineToRelative(14.0f)
|
||||
curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f)
|
||||
horizontalLineToRelative(7.0f)
|
||||
curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f)
|
||||
lineTo(23.0f, 5.0f)
|
||||
curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f)
|
||||
close()
|
||||
moveTo(21.0f, 19.0f)
|
||||
lineTo(14.0f, 19.0f)
|
||||
lineTo(14.0f, 5.0f)
|
||||
horizontalLineToRelative(7.0f)
|
||||
verticalLineToRelative(14.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
return _doubleImage!!
|
||||
}
|
||||
|
||||
open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAware {
|
||||
override val di by closestDI(app)
|
||||
@@ -155,7 +248,8 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@ExperimentalMaterialApi
|
||||
@ExperimentalFoundationApi
|
||||
@Composable
|
||||
fun ReaderBase(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -164,9 +258,26 @@ fun ReaderBase(
|
||||
val context = LocalContext.current
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
val snackbarCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
var scrollDirection by remember { mutableStateOf(0f) }
|
||||
val handleOffset by animateDpAsState(if (model.fullscreen || scrollDirection < 0f) (-36).dp else 0.dp)
|
||||
|
||||
val mainReaderOptions by remember {
|
||||
context.settingsDataStore.data.map { it.mainReaderOption }
|
||||
}.collectAsState(ReaderOptions.getDefaultInstance())
|
||||
|
||||
val nestedScrollConnection = remember { object: NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
scrollDirection = available.y.sign
|
||||
|
||||
return Offset.Zero
|
||||
}
|
||||
} }
|
||||
|
||||
LaunchedEffect(model.fullscreen) {
|
||||
context.activity?.window?.let { window ->
|
||||
ViewCompat.getWindowInsetsController(window.decorView)?.let {
|
||||
@@ -191,78 +302,266 @@ fun ReaderBase(
|
||||
}
|
||||
|
||||
Box(modifier) {
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.align(Alignment.TopStart),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars)
|
||||
) {
|
||||
itemsIndexed(model.imageList) { i, uri ->
|
||||
val state = rememberSubSampledImageState(ScaleTypes.FIT_WIDTH)
|
||||
ModalTopSheetLayout(
|
||||
modifier = Modifier.offset(0.dp, handleOffset),
|
||||
drawerContent = {
|
||||
CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.h6) {
|
||||
Column(Modifier.padding(16.dp, 0.dp)) {
|
||||
val layout = mainReaderOptions.layout
|
||||
val snap = mainReaderOptions.snap
|
||||
val orientation = mainReaderOptions.orientation
|
||||
val padding = mainReaderOptions.padding
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.wrapContentHeight(state, 500.dp)
|
||||
.fillMaxWidth()
|
||||
.border(1.dp, Color.Gray),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val progress = model.progressList.getOrNull(i) ?: 0f
|
||||
|
||||
if (progress == Float.NEGATIVE_INFINITY)
|
||||
Icon(Icons.Filled.BrokenImage, null, tint = Orange500)
|
||||
else if (progress.isFinite())
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
LinearProgressIndicator(progress)
|
||||
Text((i + 1).toString())
|
||||
}
|
||||
else if (uri != null && progress == Float.POSITIVE_INFINITY) {
|
||||
val imageSource = kotlin.runCatching {
|
||||
rememberFileXImageSource(FileX(context, uri))
|
||||
}.getOrNull()
|
||||
Text("Layout")
|
||||
|
||||
if (imageSource != null)
|
||||
SubSampledImage(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.run {
|
||||
if (model.fullscreen)
|
||||
doubleClickCycleZoom(state, 2f)
|
||||
else
|
||||
combinedClickable(
|
||||
onLongClick = {
|
||||
|
||||
}
|
||||
) {
|
||||
model.fullscreen = true
|
||||
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()
|
||||
}
|
||||
},
|
||||
imageSource = imageSource,
|
||||
state = state,
|
||||
onError = {
|
||||
model.error(i)
|
||||
}
|
||||
}) {
|
||||
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(
|
||||
Modifier
|
||||
.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)
|
||||
|
||||
if (model.progressList.any { it.isFinite() })
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.TopCenter),
|
||||
progress = model.progressList.map { if (it.isInfinite()) 1f else abs(it) }.sum() / model.progressList.size,
|
||||
color = MaterialTheme.colors.secondary
|
||||
Box(
|
||||
Modifier
|
||||
.wrapContentHeight(state, 500.dp)
|
||||
.fillMaxWidth()
|
||||
.border(1.dp, Color.Gray),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
val progress = model.progressList.getOrNull(i) ?: 0f
|
||||
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (model.progressList.any { it.isFinite() })
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.TopCenter),
|
||||
progress = model.progressList.map { if (it.isInfinite()) 1f else abs(it) }
|
||||
.sum() / model.progressList.size,
|
||||
color = MaterialTheme.colors.secondary
|
||||
)
|
||||
|
||||
SnackbarHost(
|
||||
scaffoldState.snackbarHostState,
|
||||
modifier = Modifier.align(Alignment.BottomCenter)
|
||||
)
|
||||
|
||||
SnackbarHost(
|
||||
scaffoldState.snackbarHostState,
|
||||
modifier = Modifier.align(Alignment.BottomCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package xyz.quaver.pupil.sources.hitomi
|
||||
|
||||
import android.app.Application
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -66,6 +67,7 @@ import xyz.quaver.pupil.sources.hitomi.lib.getReferer
|
||||
import xyz.quaver.pupil.sources.hitomi.lib.imageUrlFromImage
|
||||
import xyz.quaver.pupil.ui.theme.Orange500
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
||||
class Hitomi(app: Application) : Source(), DIAware {
|
||||
override val di by closestDI(app)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package xyz.quaver.pupil.sources.manatoki
|
||||
|
||||
import android.app.Application
|
||||
import android.util.LruCache
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
@@ -47,6 +46,9 @@ import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
@@ -72,8 +74,6 @@ import com.google.accompanist.insets.ui.TopAppBar
|
||||
import io.ktor.client.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.compose.rememberInstance
|
||||
@@ -89,6 +89,7 @@ import xyz.quaver.pupil.sources.manatoki.composable.*
|
||||
import xyz.quaver.pupil.sources.manatoki.viewmodel.*
|
||||
import xyz.quaver.pupil.ui.theme.Orange500
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sign
|
||||
|
||||
private val imageUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Mobile Safari/537.36"
|
||||
|
||||
@@ -413,6 +414,15 @@ class Manatoki(app: Application) : Source(), DIAware {
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
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 {
|
||||
when {
|
||||
sheetState.isVisible -> coroutineScope.launch { sheetState.hide() }
|
||||
@@ -492,7 +502,7 @@ class Manatoki(app: Application) : Source(), DIAware {
|
||||
},
|
||||
floatingActionButton = {
|
||||
AnimatedVisibility(
|
||||
!model.fullscreen,
|
||||
!(model.fullscreen || scrollDirection < 0f),
|
||||
enter = scaleIn(),
|
||||
exit = scaleOut()
|
||||
) {
|
||||
@@ -562,7 +572,7 @@ class Manatoki(app: Application) : Source(), DIAware {
|
||||
}
|
||||
) { contentPadding ->
|
||||
ReaderBase(
|
||||
Modifier.padding(contentPadding),
|
||||
Modifier.padding(contentPadding).nestedScroll(nestedScrollConnection),
|
||||
model
|
||||
)
|
||||
}
|
||||
@@ -711,7 +721,7 @@ class Manatoki(app: Application) : Source(), DIAware {
|
||||
}
|
||||
) { contentPadding ->
|
||||
Box(Modifier.padding(contentPadding)) {
|
||||
SearchOptionDrawer(
|
||||
ModalTopSheetLayout(
|
||||
modifier = Modifier.run {
|
||||
if (drawerState.currentValue == ModalTopSheetState.Hidden)
|
||||
offset(0.dp, handleOffset)
|
||||
|
||||
@@ -5,4 +5,26 @@ option java_multiple_files = true;
|
||||
|
||||
message Settings {
|
||||
optional string recent_source = 1;
|
||||
optional ReaderOptions mainReaderOption = 2;
|
||||
optional ReaderOptions fullscreenReaderOption = 3;
|
||||
}
|
||||
|
||||
message ReaderOptions {
|
||||
enum Layout {
|
||||
AUTO = 0;
|
||||
SINGLE_PAGE = 1;
|
||||
DOUBLE_PAGE = 2;
|
||||
}
|
||||
|
||||
enum Orientation {
|
||||
VERTICAL_DOWN = 0;
|
||||
VERTICAL_UP = 1;
|
||||
HORIZONTAL_RIGHT = 2;
|
||||
HORIZONTAL_LEFT = 3;
|
||||
}
|
||||
|
||||
optional Layout layout = 1;
|
||||
optional Orientation orientation = 2;
|
||||
optional bool snap = 3;
|
||||
optional bool padding = 4;
|
||||
}
|
||||
Reference in New Issue
Block a user