[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 xyz.quaver.pupil.sources.composable.ModalTopSheetState.Hidden
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ModalTopSheetLayout(
|
class ModalTopSheetLayoutShape(
|
||||||
private val cornerRadius: Dp,
|
private val cornerRadius: Dp,
|
||||||
private val handleRadius: Dp
|
private val handleRadius: Dp
|
||||||
): Shape {
|
): Shape {
|
||||||
@@ -152,7 +152,7 @@ private fun Scrim(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ExperimentalMaterialApi
|
@ExperimentalMaterialApi
|
||||||
fun SearchOptionDrawer(
|
fun ModalTopSheetLayout(
|
||||||
drawerContent: @Composable ColumnScope.() -> Unit,
|
drawerContent: @Composable ColumnScope.() -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
drawerCornerRadius: Dp = SearchOptionDrawerDefaults.CornerRadius,
|
drawerCornerRadius: Dp = SearchOptionDrawerDefaults.CornerRadius,
|
||||||
@@ -229,7 +229,7 @@ fun SearchOptionDrawer(
|
|||||||
.onGloballyPositioned {
|
.onGloballyPositioned {
|
||||||
sheetHeight = it.size.height.toFloat()
|
sheetHeight = it.size.height.toFloat()
|
||||||
},
|
},
|
||||||
shape = ModalTopSheetLayout(drawerCornerRadius, drawerHandleRadius),
|
shape = ModalTopSheetLayoutShape(drawerCornerRadius, drawerHandleRadius),
|
||||||
elevation = drawerElevation,
|
elevation = drawerElevation,
|
||||||
color = drawerBackgroundColor,
|
color = drawerBackgroundColor,
|
||||||
contentColor = drawerContentColor
|
contentColor = drawerContentColor
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package xyz.quaver.pupil.sources.composable
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
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
|
||||||
@@ -28,11 +29,21 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
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.BrokenImage
|
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.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.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.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -50,6 +61,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@@ -63,12 +75,93 @@ import xyz.quaver.graphics.subsampledimage.*
|
|||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.db.AppDatabase
|
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.ui.theme.Orange500
|
||||||
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
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.math.abs
|
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 {
|
open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAware {
|
||||||
override val di by closestDI(app)
|
override val di by closestDI(app)
|
||||||
@@ -155,7 +248,8 @@ open class ReaderBaseViewModel(app: Application) : AndroidViewModel(app), DIAwar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@ExperimentalMaterialApi
|
||||||
|
@ExperimentalFoundationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderBase(
|
fun ReaderBase(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -164,9 +258,26 @@ fun ReaderBase(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val scaffoldState = rememberScaffoldState()
|
val scaffoldState = rememberScaffoldState()
|
||||||
val snackbarCoroutineScope = rememberCoroutineScope()
|
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) {
|
LaunchedEffect(model.fullscreen) {
|
||||||
context.activity?.window?.let { window ->
|
context.activity?.window?.let { window ->
|
||||||
ViewCompat.getWindowInsetsController(window.decorView)?.let {
|
ViewCompat.getWindowInsetsController(window.decorView)?.let {
|
||||||
@@ -191,10 +302,196 @@ fun ReaderBase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Box(modifier) {
|
Box(modifier) {
|
||||||
|
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
|
||||||
|
|
||||||
|
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(
|
LazyColumn(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.align(Alignment.TopStart),
|
.align(Alignment.TopStart)
|
||||||
|
.nestedScroll(nestedScrollConnection),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars)
|
contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars)
|
||||||
) {
|
) {
|
||||||
@@ -256,7 +553,8 @@ fun ReaderBase(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.TopCenter),
|
.align(Alignment.TopCenter),
|
||||||
progress = model.progressList.map { if (it.isInfinite()) 1f else abs(it) }.sum() / model.progressList.size,
|
progress = model.progressList.map { if (it.isInfinite()) 1f else abs(it) }
|
||||||
|
.sum() / model.progressList.size,
|
||||||
color = MaterialTheme.colors.secondary
|
color = MaterialTheme.colors.secondary
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -266,3 +564,4 @@ fun ReaderBase(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ package xyz.quaver.pupil.sources.hitomi
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
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.sources.hitomi.lib.imageUrlFromImage
|
||||||
import xyz.quaver.pupil.ui.theme.Orange500
|
import xyz.quaver.pupil.ui.theme.Orange500
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
||||||
class Hitomi(app: Application) : Source(), DIAware {
|
class Hitomi(app: Application) : Source(), DIAware {
|
||||||
override val di by closestDI(app)
|
override val di by closestDI(app)
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
package xyz.quaver.pupil.sources.manatoki
|
package xyz.quaver.pupil.sources.manatoki
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.util.LruCache
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
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.Offset
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
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.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
@@ -72,8 +74,6 @@ import com.google.accompanist.insets.ui.TopAppBar
|
|||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.compose.rememberInstance
|
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.sources.manatoki.viewmodel.*
|
||||||
import xyz.quaver.pupil.ui.theme.Orange500
|
import xyz.quaver.pupil.ui.theme.Orange500
|
||||||
import kotlin.math.max
|
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"
|
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()
|
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 {
|
BackHandler {
|
||||||
when {
|
when {
|
||||||
sheetState.isVisible -> coroutineScope.launch { sheetState.hide() }
|
sheetState.isVisible -> coroutineScope.launch { sheetState.hide() }
|
||||||
@@ -492,7 +502,7 @@ class Manatoki(app: Application) : Source(), DIAware {
|
|||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
!model.fullscreen,
|
!(model.fullscreen || scrollDirection < 0f),
|
||||||
enter = scaleIn(),
|
enter = scaleIn(),
|
||||||
exit = scaleOut()
|
exit = scaleOut()
|
||||||
) {
|
) {
|
||||||
@@ -562,7 +572,7 @@ class Manatoki(app: Application) : Source(), DIAware {
|
|||||||
}
|
}
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
ReaderBase(
|
ReaderBase(
|
||||||
Modifier.padding(contentPadding),
|
Modifier.padding(contentPadding).nestedScroll(nestedScrollConnection),
|
||||||
model
|
model
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -711,7 +721,7 @@ class Manatoki(app: Application) : Source(), DIAware {
|
|||||||
}
|
}
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
Box(Modifier.padding(contentPadding)) {
|
Box(Modifier.padding(contentPadding)) {
|
||||||
SearchOptionDrawer(
|
ModalTopSheetLayout(
|
||||||
modifier = Modifier.run {
|
modifier = Modifier.run {
|
||||||
if (drawerState.currentValue == ModalTopSheetState.Hidden)
|
if (drawerState.currentValue == ModalTopSheetState.Hidden)
|
||||||
offset(0.dp, handleOffset)
|
offset(0.dp, handleOffset)
|
||||||
|
|||||||
@@ -5,4 +5,26 @@ option java_multiple_files = true;
|
|||||||
|
|
||||||
message Settings {
|
message Settings {
|
||||||
optional string recent_source = 1;
|
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