Add a scrollbar to the article screen (#63)
Co-authored-by: Matt Vaughn <matt.vaughn@willowtreeapps.com>
This commit is contained in:
parent
b292535ab6
commit
b7813d45f4
|
@ -149,6 +149,7 @@ dependencies {
|
||||||
implementation "androidx.compose.animation:animation-graphics:$compose"
|
implementation "androidx.compose.animation:animation-graphics:$compose"
|
||||||
// https://developer.android.com/jetpack/androidx/releases/compose-ui
|
// https://developer.android.com/jetpack/androidx/releases/compose-ui
|
||||||
implementation "androidx.compose.ui:ui:$compose"
|
implementation "androidx.compose.ui:ui:$compose"
|
||||||
|
implementation "androidx.compose.ui:ui-util:$compose"
|
||||||
// https://developer.android.com/jetpack/androidx/releases/compose-material
|
// https://developer.android.com/jetpack/androidx/releases/compose-material
|
||||||
implementation "androidx.compose.material:material:$compose"
|
implementation "androidx.compose.material:material:$compose"
|
||||||
implementation "androidx.compose.material:material-icons-extended:$compose"
|
implementation "androidx.compose.material:material-icons-extended:$compose"
|
||||||
|
|
320
app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt
Normal file
320
app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
package me.ash.reader.ui.ext
|
||||||
|
|
||||||
|
// From gist: https://gist.github.com/mxalbert1996/33a360fcab2105a31e5355af98216f5a/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022 Albert Chang
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
|
import androidx.compose.ui.draw.CacheDrawScope
|
||||||
|
import androidx.compose.ui.draw.DrawResult
|
||||||
|
import androidx.compose.ui.draw.drawWithCache
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
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.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.util.fastSumBy
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
|
fun Modifier.drawHorizontalScrollbar(
|
||||||
|
state: ScrollState,
|
||||||
|
reverseScrolling: Boolean = false
|
||||||
|
): Modifier = drawScrollbar(state, Orientation.Horizontal, reverseScrolling)
|
||||||
|
|
||||||
|
fun Modifier.drawVerticalScrollbar(
|
||||||
|
state: ScrollState,
|
||||||
|
reverseScrolling: Boolean = false
|
||||||
|
): Modifier = drawScrollbar(state, Orientation.Vertical, reverseScrolling)
|
||||||
|
|
||||||
|
fun Modifier.drawHorizontalScrollbar(
|
||||||
|
state: LazyListState,
|
||||||
|
reverseScrolling: Boolean = false
|
||||||
|
): Modifier = drawScrollbar(state, Orientation.Horizontal, reverseScrolling)
|
||||||
|
|
||||||
|
fun Modifier.drawVerticalScrollbar(
|
||||||
|
state: LazyListState,
|
||||||
|
reverseScrolling: Boolean = false
|
||||||
|
): Modifier = drawScrollbar(state, Orientation.Vertical, reverseScrolling)
|
||||||
|
|
||||||
|
private fun Modifier.drawScrollbar(
|
||||||
|
state: ScrollState,
|
||||||
|
orientation: Orientation,
|
||||||
|
reverseScrolling: Boolean
|
||||||
|
): Modifier = drawScrollbar(
|
||||||
|
orientation, reverseScrolling
|
||||||
|
) { reverseDirection, atEnd, thickness, color, alpha ->
|
||||||
|
val showScrollbar = state.maxValue > 0
|
||||||
|
val canvasSize = if (orientation == Orientation.Horizontal) size.width else size.height
|
||||||
|
val totalSize = canvasSize + state.maxValue
|
||||||
|
val thumbSize = canvasSize / totalSize * canvasSize
|
||||||
|
val startOffset = state.value / totalSize * canvasSize
|
||||||
|
val drawScrollbar = onDrawScrollbar(
|
||||||
|
orientation, reverseDirection, atEnd, showScrollbar,
|
||||||
|
thickness, color, alpha, thumbSize, startOffset
|
||||||
|
)
|
||||||
|
onDrawWithContent {
|
||||||
|
drawContent()
|
||||||
|
drawScrollbar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Modifier.drawScrollbar(
|
||||||
|
state: LazyListState,
|
||||||
|
orientation: Orientation,
|
||||||
|
reverseScrolling: Boolean
|
||||||
|
): Modifier = drawScrollbar(
|
||||||
|
orientation, reverseScrolling
|
||||||
|
) { reverseDirection, atEnd, thickness, color, alpha ->
|
||||||
|
val layoutInfo = state.layoutInfo
|
||||||
|
val viewportSize = layoutInfo.viewportEndOffset - layoutInfo.viewportStartOffset
|
||||||
|
val items = layoutInfo.visibleItemsInfo
|
||||||
|
val itemsSize = items.fastSumBy { it.size }
|
||||||
|
val showScrollbar = items.size < layoutInfo.totalItemsCount || itemsSize > viewportSize
|
||||||
|
val estimatedItemSize = if (items.isEmpty()) 0f else itemsSize.toFloat() / items.size
|
||||||
|
val totalSize = estimatedItemSize * layoutInfo.totalItemsCount
|
||||||
|
val canvasSize = if (orientation == Orientation.Horizontal) size.width else size.height
|
||||||
|
val thumbSize = viewportSize / totalSize * canvasSize
|
||||||
|
val startOffset = if (items.isEmpty()) 0f else items
|
||||||
|
.first()
|
||||||
|
.run {
|
||||||
|
(estimatedItemSize * index - offset) / totalSize * canvasSize
|
||||||
|
}
|
||||||
|
val drawScrollbar = onDrawScrollbar(
|
||||||
|
orientation, reverseDirection, atEnd, showScrollbar,
|
||||||
|
thickness, color, alpha, thumbSize, startOffset
|
||||||
|
)
|
||||||
|
onDrawWithContent {
|
||||||
|
drawContent()
|
||||||
|
drawScrollbar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CacheDrawScope.onDrawScrollbar(
|
||||||
|
orientation: Orientation,
|
||||||
|
reverseDirection: Boolean,
|
||||||
|
atEnd: Boolean,
|
||||||
|
showScrollbar: Boolean,
|
||||||
|
thickness: Float,
|
||||||
|
color: Color,
|
||||||
|
alpha: () -> Float,
|
||||||
|
thumbSize: Float,
|
||||||
|
startOffset: Float
|
||||||
|
): DrawScope.() -> Unit {
|
||||||
|
val topLeft = if (orientation == Orientation.Horizontal) {
|
||||||
|
Offset(
|
||||||
|
if (reverseDirection) size.width - startOffset - thumbSize else startOffset,
|
||||||
|
if (atEnd) size.height - thickness else 0f
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Offset(
|
||||||
|
if (atEnd) size.width - thickness else 0f,
|
||||||
|
if (reverseDirection) size.height - startOffset - thumbSize else startOffset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val size = if (orientation == Orientation.Horizontal) {
|
||||||
|
Size(thumbSize, thickness)
|
||||||
|
} else {
|
||||||
|
Size(thickness, thumbSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
if (showScrollbar) {
|
||||||
|
drawRect(
|
||||||
|
color = color,
|
||||||
|
topLeft = topLeft,
|
||||||
|
size = size,
|
||||||
|
alpha = alpha()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Modifier.drawScrollbar(
|
||||||
|
orientation: Orientation,
|
||||||
|
reverseScrolling: Boolean,
|
||||||
|
onBuildDrawCache: CacheDrawScope.(
|
||||||
|
reverseDirection: Boolean,
|
||||||
|
atEnd: Boolean,
|
||||||
|
thickness: Float,
|
||||||
|
color: Color,
|
||||||
|
alpha: () -> Float
|
||||||
|
) -> DrawResult
|
||||||
|
): Modifier = composed {
|
||||||
|
val scrolled = remember {
|
||||||
|
MutableSharedFlow<Unit>(
|
||||||
|
extraBufferCapacity = 1,
|
||||||
|
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val nestedScrollConnection = remember(orientation, scrolled) {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override fun onPostScroll(
|
||||||
|
consumed: Offset,
|
||||||
|
available: Offset,
|
||||||
|
source: NestedScrollSource
|
||||||
|
): Offset {
|
||||||
|
val delta = if (orientation == Orientation.Horizontal) consumed.x else consumed.y
|
||||||
|
if (delta != 0f) scrolled.tryEmit(Unit)
|
||||||
|
return Offset.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val alpha = remember { Animatable(0f) }
|
||||||
|
LaunchedEffect(scrolled, alpha) {
|
||||||
|
scrolled.collectLatest {
|
||||||
|
alpha.snapTo(1f)
|
||||||
|
delay(ViewConfiguration.getScrollDefaultDelay().toLong())
|
||||||
|
alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||||
|
val reverseDirection = if (orientation == Orientation.Horizontal) {
|
||||||
|
if (isLtr) reverseScrolling else !reverseScrolling
|
||||||
|
} else reverseScrolling
|
||||||
|
val atEnd = if (orientation == Orientation.Vertical) isLtr else true
|
||||||
|
|
||||||
|
// Calculate thickness here to workaround https://issuetracker.google.com/issues/206972664
|
||||||
|
val thickness = with(LocalDensity.current) { Thickness.toPx() }
|
||||||
|
val color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
||||||
|
Modifier
|
||||||
|
.nestedScroll(nestedScrollConnection)
|
||||||
|
.drawWithCache {
|
||||||
|
onBuildDrawCache(reverseDirection, atEnd, thickness, color, alpha::value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Thickness = 4.dp
|
||||||
|
private val FadeOutAnimationSpec =
|
||||||
|
tween<Float>(durationMillis = ViewConfiguration.getScrollBarFadeDuration())
|
||||||
|
|
||||||
|
@Preview(widthDp = 400, heightDp = 400, showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun ScrollbarPreview() {
|
||||||
|
val state = rememberScrollState()
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.drawVerticalScrollbar(state)
|
||||||
|
.verticalScroll(state),
|
||||||
|
) {
|
||||||
|
repeat(50) {
|
||||||
|
Text(
|
||||||
|
text = "Item ${it + 1}",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(widthDp = 400, heightDp = 400, showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun LazyListScrollbarPreview() {
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.drawVerticalScrollbar(state),
|
||||||
|
state = state
|
||||||
|
) {
|
||||||
|
items(50) {
|
||||||
|
Text(
|
||||||
|
text = "Item ${it + 1}",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(widthDp = 400, showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun HorizontalScrollbarPreview() {
|
||||||
|
val state = rememberScrollState()
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.drawHorizontalScrollbar(state)
|
||||||
|
.horizontalScroll(state)
|
||||||
|
) {
|
||||||
|
repeat(50) {
|
||||||
|
Text(
|
||||||
|
text = (it + 1).toString(),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp, vertical = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(widthDp = 400, showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun LazyListHorizontalScrollbarPreview() {
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier.drawHorizontalScrollbar(state),
|
||||||
|
state = state
|
||||||
|
) {
|
||||||
|
items(50) {
|
||||||
|
Text(
|
||||||
|
text = (it + 1).toString(),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 8.dp, vertical = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,10 @@ package me.ash.reader.ui.page.home.read
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Headphones
|
import androidx.compose.material.icons.outlined.Headphones
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
|
@ -25,6 +25,7 @@ import me.ash.reader.data.entity.ArticleWithFeed
|
||||||
import me.ash.reader.ui.component.FeedbackIconButton
|
import me.ash.reader.ui.component.FeedbackIconButton
|
||||||
import me.ash.reader.ui.component.WebView
|
import me.ash.reader.ui.component.WebView
|
||||||
import me.ash.reader.ui.ext.collectAsStateValue
|
import me.ash.reader.ui.ext.collectAsStateValue
|
||||||
|
import me.ash.reader.ui.ext.drawVerticalScrollbar
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -43,21 +44,14 @@ fun ReadPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewState.listState.isScrollInProgress) {
|
if (viewState.scrollState.isScrollInProgress) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
Log.i("RLog", "scroll: start")
|
Log.i("RLog", "scroll: start")
|
||||||
}
|
}
|
||||||
|
|
||||||
val preItemIndex by remember { mutableStateOf(viewState.listState.firstVisibleItemIndex) }
|
val preScrollOffset by remember { mutableStateOf(viewState.scrollState.value) }
|
||||||
val preScrollStartOffset by remember { mutableStateOf(viewState.listState.firstVisibleItemScrollOffset) }
|
val currentOffset = viewState.scrollState.value
|
||||||
val currentItemIndex = viewState.listState.firstVisibleItemIndex
|
isScrollDown = currentOffset > preScrollOffset
|
||||||
val currentScrollStartOffset = viewState.listState.firstVisibleItemScrollOffset
|
|
||||||
|
|
||||||
isScrollDown = when {
|
|
||||||
currentItemIndex > preItemIndex -> true
|
|
||||||
currentItemIndex < preItemIndex -> false
|
|
||||||
else -> currentScrollStartOffset > preScrollStartOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
|
@ -98,7 +92,7 @@ fun ReadPage(
|
||||||
content = viewState.content ?: "",
|
content = viewState.content ?: "",
|
||||||
articleWithFeed = viewState.articleWithFeed,
|
articleWithFeed = viewState.articleWithFeed,
|
||||||
viewState = viewState,
|
viewState = viewState,
|
||||||
LazyListState = viewState.listState,
|
scrollState = viewState.scrollState,
|
||||||
)
|
)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -182,10 +176,14 @@ private fun Content(
|
||||||
content: String,
|
content: String,
|
||||||
articleWithFeed: ArticleWithFeed?,
|
articleWithFeed: ArticleWithFeed?,
|
||||||
viewState: ReadViewState,
|
viewState: ReadViewState,
|
||||||
LazyListState: LazyListState = rememberLazyListState(),
|
scrollState: ScrollState = rememberScrollState(),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.statusBarsPadding(),
|
modifier = Modifier
|
||||||
|
.statusBarsPadding()
|
||||||
|
.navigationBarsPadding()
|
||||||
|
.drawVerticalScrollbar(scrollState)
|
||||||
|
.verticalScroll(scrollState),
|
||||||
) {
|
) {
|
||||||
if (articleWithFeed == null) {
|
if (articleWithFeed == null) {
|
||||||
Spacer(modifier = Modifier.height(64.dp))
|
Spacer(modifier = Modifier.height(64.dp))
|
||||||
|
@ -196,13 +194,8 @@ private fun Content(
|
||||||
// url = "https://assets8.lottiefiles.com/packages/lf20_jm7mv1ib.json",
|
// url = "https://assets8.lottiefiles.com/packages/lf20_jm7mv1ib.json",
|
||||||
// )
|
// )
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
Column {
|
||||||
state = LazyListState,
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(64.dp))
|
Spacer(modifier = Modifier.height(64.dp))
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -210,8 +203,6 @@ private fun Content(
|
||||||
) {
|
) {
|
||||||
Header(articleWithFeed)
|
Header(articleWithFeed)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(22.dp))
|
Spacer(modifier = Modifier.height(22.dp))
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = viewState.isLoading,
|
visible = viewState.isLoading,
|
||||||
|
@ -239,15 +230,12 @@ private fun Content(
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(50.dp))
|
Spacer(modifier = Modifier.height(50.dp))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(64.dp))
|
Spacer(modifier = Modifier.height(64.dp))
|
||||||
Spacer(modifier = Modifier.height(64.dp))
|
Spacer(modifier = Modifier.height(64.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BottomBar(
|
private fun BottomBar(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package me.ash.reader.ui.page.home.read
|
package me.ash.reader.ui.page.home.read
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -31,7 +31,6 @@ class ReadViewModel @Inject constructor(
|
||||||
is ReadViewAction.RenderFullContent -> renderFullContent()
|
is ReadViewAction.RenderFullContent -> renderFullContent()
|
||||||
is ReadViewAction.MarkUnread -> markUnread(action.isUnread)
|
is ReadViewAction.MarkUnread -> markUnread(action.isUnread)
|
||||||
is ReadViewAction.MarkStarred -> markStarred(action.isStarred)
|
is ReadViewAction.MarkStarred -> markStarred(action.isStarred)
|
||||||
is ReadViewAction.ScrollToItem -> scrollToItem(action.index)
|
|
||||||
is ReadViewAction.ClearArticle -> clearArticle()
|
is ReadViewAction.ClearArticle -> clearArticle()
|
||||||
is ReadViewAction.ChangeLoading -> changeLoading(action.isLoading)
|
is ReadViewAction.ChangeLoading -> changeLoading(action.isLoading)
|
||||||
}
|
}
|
||||||
|
@ -130,12 +129,6 @@ class ReadViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scrollToItem(index: Int) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
_viewState.value.listState.scrollToItem(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearArticle() {
|
private fun clearArticle() {
|
||||||
_viewState.update {
|
_viewState.update {
|
||||||
it.copy(articleWithFeed = null)
|
it.copy(articleWithFeed = null)
|
||||||
|
@ -153,7 +146,7 @@ data class ReadViewState(
|
||||||
val articleWithFeed: ArticleWithFeed? = null,
|
val articleWithFeed: ArticleWithFeed? = null,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val listState: LazyListState = LazyListState(),
|
val scrollState: ScrollState = ScrollState(0),
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed class ReadViewAction {
|
sealed class ReadViewAction {
|
||||||
|
@ -173,10 +166,6 @@ sealed class ReadViewAction {
|
||||||
val isStarred: Boolean,
|
val isStarred: Boolean,
|
||||||
) : ReadViewAction()
|
) : ReadViewAction()
|
||||||
|
|
||||||
data class ScrollToItem(
|
|
||||||
val index: Int
|
|
||||||
) : ReadViewAction()
|
|
||||||
|
|
||||||
object ClearArticle : ReadViewAction()
|
object ClearArticle : ReadViewAction()
|
||||||
|
|
||||||
data class ChangeLoading(
|
data class ChangeLoading(
|
||||||
|
|
Loading…
Reference in New Issue
Block a user