Optimize the reading page
This commit is contained in:
parent
ae394da387
commit
ba3620d84f
|
@ -166,9 +166,9 @@ dependencies {
|
||||||
// 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"
|
||||||
|
debugImplementation "androidx.compose.ui:ui-tooling:$compose"
|
||||||
implementation "androidx.compose.ui:ui-tooling-preview:$compose"
|
implementation "androidx.compose.ui:ui-tooling-preview:$compose"
|
||||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose"
|
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose"
|
||||||
debugImplementation "androidx.compose.ui:ui-tooling:$compose"
|
|
||||||
|
|
||||||
// hilt
|
// hilt
|
||||||
implementation "androidx.hilt:hilt-work:1.0.0"
|
implementation "androidx.hilt:hilt-work:1.0.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package me.ash.reader.ui.component.base
|
package me.ash.reader.ui.component.base
|
||||||
|
|
||||||
import androidx.compose.animation.*
|
import RYExtensibleVisibility
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -38,11 +38,7 @@ fun AnimatedPopup(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
RYExtensibleVisibility(visible = visible) {
|
||||||
visible = visible,
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package me.ash.reader.ui.component.base
|
package me.ash.reader.ui.component.base
|
||||||
|
|
||||||
import androidx.compose.animation.*
|
import RYExtensibleVisibility
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
@ -41,11 +41,7 @@ fun DisplayText(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
AnimatedVisibility(
|
RYExtensibleVisibility(visible = desc.isNotEmpty()) {
|
||||||
visible = desc.isNotEmpty(),
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.height(16.dp),
|
modifier = Modifier.height(16.dp),
|
||||||
text = desc,
|
text = desc,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package me.ash.reader.ui.component.base
|
package me.ash.reader.ui.component.base
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -10,10 +10,7 @@ import androidx.compose.ui.graphics.DefaultAlpha
|
||||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import coil.compose.rememberImagePainter
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import coil.compose.LocalImageLoader
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import coil.size.Precision
|
import coil.size.Precision
|
||||||
import coil.size.Scale
|
import coil.size.Scale
|
||||||
import coil.size.Size
|
import coil.size.Size
|
||||||
|
@ -33,34 +30,51 @@ fun RYAsyncImage(
|
||||||
@DrawableRes placeholder: Int? = R.drawable.ic_hourglass_empty_black_24dp,
|
@DrawableRes placeholder: Int? = R.drawable.ic_hourglass_empty_black_24dp,
|
||||||
@DrawableRes error: Int? = R.drawable.ic_broken_image_black_24dp,
|
@DrawableRes error: Int? = R.drawable.ic_broken_image_black_24dp,
|
||||||
) {
|
) {
|
||||||
coil.compose.AsyncImage(
|
Image(
|
||||||
modifier = modifier,
|
painter = rememberImagePainter(
|
||||||
model = ImageRequest
|
data = data,
|
||||||
.Builder(LocalContext.current)
|
builder = {
|
||||||
.data(data)
|
if (placeholder != null) placeholder(placeholder)
|
||||||
.crossfade(true)
|
if (error != null) error(error)
|
||||||
.scale(scale)
|
crossfade(true)
|
||||||
.precision(precision)
|
scale(scale)
|
||||||
.size(size)
|
precision(precision)
|
||||||
.build(),
|
size(size)
|
||||||
|
},
|
||||||
|
),
|
||||||
contentDescription = contentDescription,
|
contentDescription = contentDescription,
|
||||||
contentScale = contentScale,
|
contentScale = contentScale,
|
||||||
imageLoader = LocalImageLoader.current,
|
modifier = modifier,
|
||||||
placeholder = placeholder?.run {
|
|
||||||
forwardingPainter(
|
|
||||||
painter = painterResource(this),
|
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant),
|
|
||||||
alpha = 0.1f,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
error = error?.run {
|
|
||||||
forwardingPainter(
|
|
||||||
painter = painterResource(this),
|
|
||||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant),
|
|
||||||
alpha = 0.1f,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// coil.compose.AsyncImage(
|
||||||
|
// modifier = modifier,
|
||||||
|
// model = ImageRequest
|
||||||
|
// .Builder(LocalContext.current)
|
||||||
|
// .data(data)
|
||||||
|
// .crossfade(true)
|
||||||
|
// .scale(scale)
|
||||||
|
// .precision(precision)
|
||||||
|
// .size(size)
|
||||||
|
// .build(),
|
||||||
|
// contentDescription = contentDescription,
|
||||||
|
// contentScale = contentScale,
|
||||||
|
// imageLoader = LocalImageLoader.current,
|
||||||
|
// placeholder = placeholder?.run {
|
||||||
|
// forwardingPainter(
|
||||||
|
// painter = painterResource(this),
|
||||||
|
// colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant),
|
||||||
|
// alpha = 0.1f,
|
||||||
|
// )
|
||||||
|
// },
|
||||||
|
// error = error?.run {
|
||||||
|
// forwardingPainter(
|
||||||
|
// painter = painterResource(this),
|
||||||
|
// colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant),
|
||||||
|
// alpha = 0.1f,
|
||||||
|
// )
|
||||||
|
// },
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
// From: https://gist.github.com/colinrtwhite/c2966e0b8584b4cdf0a5b05786b20ae1
|
// From: https://gist.github.com/colinrtwhite/c2966e0b8584b4cdf0a5b05786b20ae1
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RYExtensibleVisibility(
|
||||||
|
visible: Boolean,
|
||||||
|
content: @Composable AnimatedVisibilityScope.() -> Unit
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = fadeIn() + expandVertically(),
|
||||||
|
exit = fadeOut() + shrinkVertically(),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ import me.ash.reader.ui.theme.palette.alwaysLight
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectionChip(
|
fun RYSelectionChip(
|
||||||
content: String,
|
content: String,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
|
@ -27,7 +27,8 @@ import android.util.Log
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
|
|
||||||
fun LazyListScope.reader(
|
@Suppress("FunctionName")
|
||||||
|
fun LazyListScope.Reader(
|
||||||
context: Context,
|
context: Context,
|
||||||
link: String,
|
link: String,
|
||||||
content: String,
|
content: String,
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package me.ash.reader.ui.ext
|
package me.ash.reader.ui.ext
|
||||||
|
|
||||||
import androidx.compose.material3.ColorScheme
|
import androidx.compose.material3.ColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlin.math.ln
|
import kotlin.math.ln
|
||||||
|
|
||||||
|
@Composable
|
||||||
fun ColorScheme.surfaceColorAtElevation(
|
fun ColorScheme.surfaceColorAtElevation(
|
||||||
elevation: Dp,
|
elevation: Dp,
|
||||||
color: Color = surface,
|
color: Color = surface,
|
||||||
): Color = color.atElevation(surfaceTint, elevation)
|
): Color = remember(this, elevation, color) { color.atElevation(surfaceTint, elevation) }
|
||||||
|
|
||||||
fun Color.atElevation(
|
fun Color.atElevation(
|
||||||
sourceColor: Color,
|
sourceColor: Color,
|
||||||
|
|
|
@ -4,9 +4,11 @@ import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import me.ash.reader.R
|
||||||
import me.ash.reader.data.model.Version
|
import me.ash.reader.data.model.Version
|
||||||
import me.ash.reader.data.model.toVersion
|
import me.ash.reader.data.model.toVersion
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -53,4 +55,19 @@ fun Context.showToast(message: String?, duration: Int = Toast.LENGTH_SHORT) {
|
||||||
|
|
||||||
fun Context.showToastLong(message: String?) {
|
fun Context.showToastLong(message: String?) {
|
||||||
showToast(message, Toast.LENGTH_LONG)
|
showToast(message, Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.share(content: String) {
|
||||||
|
startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
|
||||||
|
putExtra(
|
||||||
|
Intent.EXTRA_TEXT,
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
type = "text/plain"
|
||||||
|
}, getString(R.string.share)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.openURL(url: String? = null) {
|
||||||
|
url?.takeIf { it.trim().isNotEmpty() }
|
||||||
|
?.let { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it))) }
|
||||||
}
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
package me.ash.reader.ui.ext
|
package me.ash.reader.ui.ext
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@ -27,4 +26,34 @@ fun <T : Any> LazyPagingItems<T>.rememberLazyListState(): LazyListState {
|
||||||
// Return rememberLazyListState (normal case).
|
// Return rememberLazyListState (normal case).
|
||||||
else -> androidx.compose.foundation.lazy.rememberLazyListState()
|
else -> androidx.compose.foundation.lazy.rememberLazyListState()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: To be improved
|
||||||
|
*
|
||||||
|
* Returns whether the LazyListState is currently in the
|
||||||
|
* downward scrolling state.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun LazyListState.isScrollDown(): Boolean {
|
||||||
|
var isScrollDown by remember { mutableStateOf(false) }
|
||||||
|
var preItemIndex by remember { mutableStateOf(0) }
|
||||||
|
var preScrollStartOffset by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
|
LaunchedEffect(this) {
|
||||||
|
snapshotFlow { isScrollInProgress }.collect {
|
||||||
|
if (isScrollInProgress) {
|
||||||
|
isScrollDown = when {
|
||||||
|
firstVisibleItemIndex > preItemIndex -> true
|
||||||
|
firstVisibleItemScrollOffset < preItemIndex -> false
|
||||||
|
else -> firstVisibleItemScrollOffset > preScrollStartOffset
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
preItemIndex = firstVisibleItemIndex
|
||||||
|
preScrollStartOffset = firstVisibleItemScrollOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isScrollDown
|
||||||
}
|
}
|
|
@ -52,6 +52,7 @@ import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.draw.CacheDrawScope
|
import androidx.compose.ui.draw.CacheDrawScope
|
||||||
import androidx.compose.ui.draw.DrawResult
|
import androidx.compose.ui.draw.DrawResult
|
||||||
import androidx.compose.ui.draw.drawWithCache
|
import androidx.compose.ui.draw.drawWithCache
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
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
|
||||||
|
@ -173,11 +174,15 @@ private fun CacheDrawScope.onDrawScrollbar(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
if (showScrollbar) {
|
if (showScrollbar) {
|
||||||
drawRect(
|
drawRoundRect(
|
||||||
color = color,
|
color = color,
|
||||||
topLeft = topLeft,
|
topLeft = topLeft,
|
||||||
size = size,
|
size = size,
|
||||||
alpha = alpha()
|
alpha = alpha(),
|
||||||
|
cornerRadius = CornerRadius(
|
||||||
|
x = size.width,
|
||||||
|
y = size.width,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,7 +222,7 @@ private fun Modifier.drawScrollbar(
|
||||||
val alpha = remember { Animatable(0f) }
|
val alpha = remember { Animatable(0f) }
|
||||||
LaunchedEffect(scrolled, alpha) {
|
LaunchedEffect(scrolled, alpha) {
|
||||||
scrolled.collectLatest {
|
scrolled.collectLatest {
|
||||||
alpha.snapTo(1f)
|
alpha.snapTo(0.3f)
|
||||||
delay(ViewConfiguration.getScrollDefaultDelay().toLong())
|
delay(ViewConfiguration.getScrollDefaultDelay().toLong())
|
||||||
alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec)
|
alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec)
|
||||||
}
|
}
|
||||||
|
@ -231,7 +236,7 @@ private fun Modifier.drawScrollbar(
|
||||||
|
|
||||||
// Calculate thickness here to workaround https://issuetracker.google.com/issues/206972664
|
// Calculate thickness here to workaround https://issuetracker.google.com/issues/206972664
|
||||||
val thickness = with(LocalDensity.current) { Thickness.toPx() }
|
val thickness = with(LocalDensity.current) { Thickness.toPx() }
|
||||||
val color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
|
val color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
Modifier
|
Modifier
|
||||||
.nestedScroll(nestedScrollConnection)
|
.nestedScroll(nestedScrollConnection)
|
||||||
.drawWithCache {
|
.drawWithCache {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package me.ash.reader.ui.page.home.feeds
|
package me.ash.reader.ui.page.home.feeds
|
||||||
|
|
||||||
|
import RYExtensibleVisibility
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import androidx.compose.animation.*
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
@ -104,11 +104,7 @@ fun GroupItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(22.dp))
|
Spacer(modifier = Modifier.height(22.dp))
|
||||||
AnimatedVisibility(
|
RYExtensibleVisibility(visible = expanded) {
|
||||||
visible = expanded,
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
Column {
|
Column {
|
||||||
feeds.forEach { feed ->
|
feeds.forEach { feed ->
|
||||||
FeedItem(
|
FeedItem(
|
||||||
|
|
|
@ -34,7 +34,7 @@ import kotlinx.coroutines.launch
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
import me.ash.reader.data.entity.Group
|
import me.ash.reader.data.entity.Group
|
||||||
import me.ash.reader.ui.component.base.BottomDrawer
|
import me.ash.reader.ui.component.base.BottomDrawer
|
||||||
import me.ash.reader.ui.component.base.SelectionChip
|
import me.ash.reader.ui.component.base.RYSelectionChip
|
||||||
import me.ash.reader.ui.component.base.Subtitle
|
import me.ash.reader.ui.component.base.Subtitle
|
||||||
import me.ash.reader.ui.component.base.TextFieldDialog
|
import me.ash.reader.ui.component.base.TextFieldDialog
|
||||||
import me.ash.reader.ui.ext.*
|
import me.ash.reader.ui.ext.*
|
||||||
|
@ -162,7 +162,7 @@ private fun Preset(
|
||||||
crossAxisSpacing = 10.dp,
|
crossAxisSpacing = 10.dp,
|
||||||
mainAxisSpacing = 10.dp,
|
mainAxisSpacing = 10.dp,
|
||||||
) {
|
) {
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = stringResource(R.string.allow_notification),
|
content = stringResource(R.string.allow_notification),
|
||||||
selected = false,
|
selected = false,
|
||||||
|
@ -178,7 +178,7 @@ private fun Preset(
|
||||||
) {
|
) {
|
||||||
groupOptionViewModel.showAllAllowNotificationDialog()
|
groupOptionViewModel.showAllAllowNotificationDialog()
|
||||||
}
|
}
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = stringResource(R.string.parse_full_content),
|
content = stringResource(R.string.parse_full_content),
|
||||||
selected = false,
|
selected = false,
|
||||||
|
@ -194,7 +194,7 @@ private fun Preset(
|
||||||
) {
|
) {
|
||||||
groupOptionViewModel.showAllParseFullContentDialog()
|
groupOptionViewModel.showAllParseFullContentDialog()
|
||||||
}
|
}
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = stringResource(R.string.clear_articles),
|
content = stringResource(R.string.clear_articles),
|
||||||
selected = false,
|
selected = false,
|
||||||
|
@ -202,7 +202,7 @@ private fun Preset(
|
||||||
groupOptionViewModel.showClearDialog()
|
groupOptionViewModel.showClearDialog()
|
||||||
}
|
}
|
||||||
if (group?.id != context.currentAccountId.getDefaultGroupId()) {
|
if (group?.id != context.currentAccountId.getDefaultGroupId()) {
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = stringResource(R.string.delete_group),
|
content = stringResource(R.string.delete_group),
|
||||||
selected = false,
|
selected = false,
|
||||||
|
@ -227,7 +227,7 @@ private fun FlowRowGroups(
|
||||||
) {
|
) {
|
||||||
groupOptionUiState.groups.forEach {
|
groupOptionUiState.groups.forEach {
|
||||||
if (it.id != group?.id) {
|
if (it.id != group?.id) {
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = it.name,
|
content = it.name,
|
||||||
selected = false,
|
selected = false,
|
||||||
|
@ -248,7 +248,7 @@ private fun LazyRowGroups(
|
||||||
LazyRow {
|
LazyRow {
|
||||||
items(groupOptionUiState.groups) {
|
items(groupOptionUiState.groups) {
|
||||||
if (it.id != group?.id) {
|
if (it.id != group?.id) {
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = it.name,
|
content = it.name,
|
||||||
selected = false,
|
selected = false,
|
||||||
|
|
|
@ -30,7 +30,7 @@ import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.google.accompanist.flowlayout.MainAxisAlignment
|
import com.google.accompanist.flowlayout.MainAxisAlignment
|
||||||
import me.ash.reader.R
|
import me.ash.reader.R
|
||||||
import me.ash.reader.data.entity.Group
|
import me.ash.reader.data.entity.Group
|
||||||
import me.ash.reader.ui.component.base.SelectionChip
|
import me.ash.reader.ui.component.base.RYSelectionChip
|
||||||
import me.ash.reader.ui.component.base.Subtitle
|
import me.ash.reader.ui.component.base.Subtitle
|
||||||
import me.ash.reader.ui.ext.roundClick
|
import me.ash.reader.ui.ext.roundClick
|
||||||
import me.ash.reader.ui.theme.palette.alwaysLight
|
import me.ash.reader.ui.theme.palette.alwaysLight
|
||||||
|
@ -127,7 +127,7 @@ private fun Preset(
|
||||||
crossAxisSpacing = 10.dp,
|
crossAxisSpacing = 10.dp,
|
||||||
mainAxisSpacing = 10.dp,
|
mainAxisSpacing = 10.dp,
|
||||||
) {
|
) {
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = stringResource(R.string.allow_notification),
|
content = stringResource(R.string.allow_notification),
|
||||||
selected = selectedAllowNotificationPreset,
|
selected = selectedAllowNotificationPreset,
|
||||||
|
@ -144,7 +144,7 @@ private fun Preset(
|
||||||
) {
|
) {
|
||||||
allowNotificationPresetOnClick()
|
allowNotificationPresetOnClick()
|
||||||
}
|
}
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = stringResource(R.string.parse_full_content),
|
content = stringResource(R.string.parse_full_content),
|
||||||
selected = selectedParseFullContentPreset,
|
selected = selectedParseFullContentPreset,
|
||||||
|
@ -162,14 +162,14 @@ private fun Preset(
|
||||||
parseFullContentPresetOnClick()
|
parseFullContentPresetOnClick()
|
||||||
}
|
}
|
||||||
if (showUnsubscribe) {
|
if (showUnsubscribe) {
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = stringResource(R.string.clear_articles),
|
content = stringResource(R.string.clear_articles),
|
||||||
selected = false,
|
selected = false,
|
||||||
) {
|
) {
|
||||||
clearArticlesOnClick()
|
clearArticlesOnClick()
|
||||||
}
|
}
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = stringResource(R.string.unsubscribe),
|
content = stringResource(R.string.unsubscribe),
|
||||||
selected = false,
|
selected = false,
|
||||||
|
@ -196,7 +196,7 @@ private fun AddToGroup(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
items(groups) {
|
items(groups) {
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = it.name,
|
content = it.name,
|
||||||
selected = it.id == selectedGroupId,
|
selected = it.id == selectedGroupId,
|
||||||
|
@ -215,7 +215,7 @@ private fun AddToGroup(
|
||||||
mainAxisSpacing = 10.dp,
|
mainAxisSpacing = 10.dp,
|
||||||
) {
|
) {
|
||||||
groups.forEach {
|
groups.forEach {
|
||||||
SelectionChip(
|
RYSelectionChip(
|
||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
content = it.name,
|
content = it.name,
|
||||||
selected = it.id == selectedGroupId,
|
selected = it.id == selectedGroupId,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package me.ash.reader.ui.page.home.flow
|
package me.ash.reader.ui.page.home.flow
|
||||||
|
|
||||||
|
import RYExtensibleVisibility
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.*
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
@ -19,7 +19,6 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.paging.LoadState
|
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -121,11 +120,7 @@ fun FlowPage(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
AnimatedVisibility(
|
RYExtensibleVisibility(visible = !filterUiState.filter.isStarred()) {
|
||||||
visible = !filterUiState.filter.isStarred(),
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
FeedbackIconButton(
|
FeedbackIconButton(
|
||||||
imageVector = Icons.Rounded.DoneAll,
|
imageVector = Icons.Rounded.DoneAll,
|
||||||
contentDescription = stringResource(R.string.mark_all_as_read),
|
contentDescription = stringResource(R.string.mark_all_as_read),
|
||||||
|
@ -171,11 +166,7 @@ fun FlowPage(
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
DisplayTextHeader(filterUiState, isSyncing, articleListFeedIcon.value)
|
DisplayTextHeader(filterUiState, isSyncing, articleListFeedIcon.value)
|
||||||
AnimatedVisibility(
|
RYExtensibleVisibility(visible = markAsRead) {
|
||||||
visible = markAsRead,
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.height((56 + 24 + 10).dp))
|
Spacer(modifier = Modifier.height((56 + 24 + 10).dp))
|
||||||
}
|
}
|
||||||
MarkAsReadBar(
|
MarkAsReadBar(
|
||||||
|
@ -193,11 +184,7 @@ fun FlowPage(
|
||||||
markAsReadBefore = it,
|
markAsReadBefore = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AnimatedVisibility(
|
RYExtensibleVisibility(visible = onSearch) {
|
||||||
visible = onSearch,
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
SearchBar(
|
SearchBar(
|
||||||
value = homeUiState.searchContent,
|
value = homeUiState.searchContent,
|
||||||
placeholder = when {
|
placeholder = when {
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
package me.ash.reader.ui.page.home.reading
|
||||||
|
|
||||||
|
import RYExtensibleVisibility
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.FiberManualRecord
|
||||||
|
import androidx.compose.material.icons.outlined.Article
|
||||||
|
import androidx.compose.material.icons.outlined.FiberManualRecord
|
||||||
|
import androidx.compose.material.icons.outlined.Headphones
|
||||||
|
import androidx.compose.material.icons.rounded.Article
|
||||||
|
import androidx.compose.material.icons.rounded.ExpandMore
|
||||||
|
import androidx.compose.material.icons.rounded.Star
|
||||||
|
import androidx.compose.material.icons.rounded.StarOutline
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import me.ash.reader.R
|
||||||
|
import me.ash.reader.ui.component.base.CanBeDisabledIconButton
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BottomBar(
|
||||||
|
isShow: Boolean,
|
||||||
|
isUnread: Boolean,
|
||||||
|
isStarred: Boolean,
|
||||||
|
isFullContent: Boolean,
|
||||||
|
onUnread: (isUnread: Boolean) -> Unit = {},
|
||||||
|
onStarred: (isStarred: Boolean) -> Unit = {},
|
||||||
|
onFullContent: (isFullContent: Boolean) -> Unit = {},
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(1f),
|
||||||
|
contentAlignment = Alignment.BottomCenter
|
||||||
|
) {
|
||||||
|
RYExtensibleVisibility(visible = isShow) {
|
||||||
|
val view = LocalView.current
|
||||||
|
|
||||||
|
Surface(modifier = Modifier.navigationBarsPadding()) {
|
||||||
|
// TODO: Component styles await refactoring
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(60.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceAround,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
CanBeDisabledIconButton(
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
disabled = false,
|
||||||
|
imageVector = if (isUnread) {
|
||||||
|
Icons.Filled.FiberManualRecord
|
||||||
|
} else {
|
||||||
|
Icons.Outlined.FiberManualRecord
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(if (isUnread) R.string.mark_as_read else R.string.mark_as_unread),
|
||||||
|
tint = if (isUnread) {
|
||||||
|
MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.outline
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
|
onUnread(!isUnread)
|
||||||
|
}
|
||||||
|
CanBeDisabledIconButton(
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
disabled = false,
|
||||||
|
imageVector = if (isStarred) {
|
||||||
|
Icons.Rounded.Star
|
||||||
|
} else {
|
||||||
|
Icons.Rounded.StarOutline
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(if (isStarred) R.string.mark_as_unstar else R.string.mark_as_starred),
|
||||||
|
tint = if (isStarred) {
|
||||||
|
MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.outline
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
|
onStarred(!isStarred)
|
||||||
|
}
|
||||||
|
CanBeDisabledIconButton(
|
||||||
|
disabled = true,
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
imageVector = Icons.Rounded.ExpandMore,
|
||||||
|
contentDescription = "Next Article",
|
||||||
|
tint = MaterialTheme.colorScheme.outline,
|
||||||
|
) {
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
|
}
|
||||||
|
CanBeDisabledIconButton(
|
||||||
|
modifier = Modifier.size(36.dp),
|
||||||
|
disabled = true,
|
||||||
|
imageVector = Icons.Outlined.Headphones,
|
||||||
|
contentDescription = "Add Tag",
|
||||||
|
tint = MaterialTheme.colorScheme.outline,
|
||||||
|
) {
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
|
}
|
||||||
|
CanBeDisabledIconButton(
|
||||||
|
disabled = false,
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
imageVector = if (isFullContent) {
|
||||||
|
Icons.Rounded.Article
|
||||||
|
} else {
|
||||||
|
Icons.Outlined.Article
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(R.string.parse_full_content),
|
||||||
|
tint = if (isFullContent) {
|
||||||
|
MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.outline
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
|
onFullContent(!isFullContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt
Normal file
101
app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package me.ash.reader.ui.page.home.reading
|
||||||
|
|
||||||
|
import RYExtensibleVisibility
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.text.selection.DisableSelection
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import me.ash.reader.ui.component.reader.Reader
|
||||||
|
import me.ash.reader.ui.ext.drawVerticalScrollbar
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Content(
|
||||||
|
content: String,
|
||||||
|
feedName: String,
|
||||||
|
title: String,
|
||||||
|
author: String? = null,
|
||||||
|
link: String? = null,
|
||||||
|
publishedDate: Date,
|
||||||
|
listState: LazyListState,
|
||||||
|
isLoading: Boolean,
|
||||||
|
isShowToolBar: Boolean,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
SelectionContainer {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.statusBarsPadding()
|
||||||
|
.run {
|
||||||
|
if (isShowToolBar) {
|
||||||
|
navigationBarsPadding()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drawVerticalScrollbar(listState),
|
||||||
|
state = listState,
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
// Top bar height
|
||||||
|
Spacer(modifier = Modifier.height(64.dp))
|
||||||
|
// padding
|
||||||
|
Spacer(modifier = Modifier.height(22.dp))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
) {
|
||||||
|
DisableSelection {
|
||||||
|
Header(
|
||||||
|
feedName = feedName,
|
||||||
|
title = title,
|
||||||
|
author = author,
|
||||||
|
link = link,
|
||||||
|
publishedDate = publishedDate,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(22.dp))
|
||||||
|
RYExtensibleVisibility(visible = isLoading) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Spacer(modifier = Modifier.height(22.dp))
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(30.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(22.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isLoading) {
|
||||||
|
Reader(
|
||||||
|
context = context,
|
||||||
|
link = link ?: "",
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(128.dp))
|
||||||
|
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,62 +1,67 @@
|
||||||
package me.ash.reader.ui.page.home.reading
|
package me.ash.reader.ui.page.home.reading
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import me.ash.reader.data.entity.ArticleWithFeed
|
|
||||||
import me.ash.reader.ui.ext.formatAsString
|
import me.ash.reader.ui.ext.formatAsString
|
||||||
|
import me.ash.reader.ui.ext.openURL
|
||||||
import me.ash.reader.ui.ext.roundClick
|
import me.ash.reader.ui.ext.roundClick
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Header(
|
fun Header(
|
||||||
articleWithFeed: ArticleWithFeed,
|
feedName: String,
|
||||||
|
title: String,
|
||||||
|
author: String? = null,
|
||||||
|
link: String? = null,
|
||||||
|
publishedDate: Date,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val dateString = remember(publishedDate) {
|
||||||
|
publishedDate.formatAsString(context, atHourMinute = true)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.roundClick {
|
.roundClick {
|
||||||
articleWithFeed.article.link.let {
|
context.openURL(link)
|
||||||
if (it.isNotEmpty()) {
|
|
||||||
context.startActivity(
|
|
||||||
Intent(Intent.ACTION_VIEW, Uri.parse(articleWithFeed.article.link))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = articleWithFeed.article.date.formatAsString(context, atHourMinute = true),
|
modifier = Modifier.alpha(0.7f),
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
text = dateString,
|
||||||
|
color = MaterialTheme.colorScheme.outline,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = articleWithFeed.article.title,
|
text = title,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style = MaterialTheme.typography.headlineLarge,
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
articleWithFeed.article.author?.let {
|
author?.let {
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
|
modifier = Modifier.alpha(0.7f),
|
||||||
text = it,
|
text = it,
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
color = MaterialTheme.colorScheme.outline,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = articleWithFeed.feed.name,
|
modifier = Modifier.alpha(0.7f),
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
text = feedName,
|
||||||
|
color = MaterialTheme.colorScheme.outline,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,140 +0,0 @@
|
||||||
package me.ash.reader.ui.page.home.reading
|
|
||||||
|
|
||||||
import android.view.HapticFeedbackConstants
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.FiberManualRecord
|
|
||||||
import androidx.compose.material.icons.outlined.Article
|
|
||||||
import androidx.compose.material.icons.outlined.FiberManualRecord
|
|
||||||
import androidx.compose.material.icons.outlined.Headphones
|
|
||||||
import androidx.compose.material.icons.rounded.Article
|
|
||||||
import androidx.compose.material.icons.rounded.ExpandMore
|
|
||||||
import androidx.compose.material.icons.rounded.Star
|
|
||||||
import androidx.compose.material.icons.rounded.StarOutline
|
|
||||||
import androidx.compose.material3.Divider
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import me.ash.reader.R
|
|
||||||
import me.ash.reader.ui.component.base.CanBeDisabledIconButton
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ReadingBar(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
disabled: Boolean,
|
|
||||||
isUnread: Boolean,
|
|
||||||
isStarred: Boolean,
|
|
||||||
isFullContent: Boolean,
|
|
||||||
unreadOnClick: (afterIsUnread: Boolean) -> Unit = {},
|
|
||||||
starredOnClick: (afterIsStarred: Boolean) -> Unit = {},
|
|
||||||
fullContentOnClick: (afterIsFullContent: Boolean) -> Unit = {},
|
|
||||||
) {
|
|
||||||
val view = LocalView.current
|
|
||||||
var fullContent by remember { mutableStateOf(isFullContent) }
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = modifier.background(MaterialTheme.colorScheme.surface),
|
|
||||||
tonalElevation = 0.dp,
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.height(60.dp)
|
|
||||||
) {
|
|
||||||
Box {
|
|
||||||
Divider(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(1.dp)
|
|
||||||
.zIndex(1f),
|
|
||||||
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.24f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceAround,
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
CanBeDisabledIconButton(
|
|
||||||
modifier = Modifier.size(40.dp),
|
|
||||||
disabled = disabled,
|
|
||||||
imageVector = if (isUnread) {
|
|
||||||
Icons.Filled.FiberManualRecord
|
|
||||||
} else {
|
|
||||||
Icons.Outlined.FiberManualRecord
|
|
||||||
},
|
|
||||||
contentDescription = stringResource(if (isUnread) R.string.mark_as_read else R.string.mark_as_unread),
|
|
||||||
tint = if (isUnread) {
|
|
||||||
MaterialTheme.colorScheme.onSecondaryContainer
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.outline
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
|
||||||
unreadOnClick(!isUnread)
|
|
||||||
}
|
|
||||||
CanBeDisabledIconButton(
|
|
||||||
modifier = Modifier.size(40.dp),
|
|
||||||
disabled = disabled,
|
|
||||||
imageVector = if (isStarred) {
|
|
||||||
Icons.Rounded.Star
|
|
||||||
} else {
|
|
||||||
Icons.Rounded.StarOutline
|
|
||||||
},
|
|
||||||
contentDescription = stringResource(if (isStarred) R.string.mark_as_unstar else R.string.mark_as_starred),
|
|
||||||
tint = if (isStarred) {
|
|
||||||
MaterialTheme.colorScheme.onSecondaryContainer
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.outline
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
|
||||||
starredOnClick(!isStarred)
|
|
||||||
}
|
|
||||||
CanBeDisabledIconButton(
|
|
||||||
disabled = true,
|
|
||||||
modifier = Modifier.size(40.dp),
|
|
||||||
imageVector = Icons.Rounded.ExpandMore,
|
|
||||||
contentDescription = "Next Article",
|
|
||||||
tint = MaterialTheme.colorScheme.outline,
|
|
||||||
) {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
|
||||||
}
|
|
||||||
CanBeDisabledIconButton(
|
|
||||||
modifier = Modifier.size(36.dp),
|
|
||||||
disabled = true,
|
|
||||||
imageVector = Icons.Outlined.Headphones,
|
|
||||||
contentDescription = "Add Tag",
|
|
||||||
tint = MaterialTheme.colorScheme.outline,
|
|
||||||
) {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
|
||||||
}
|
|
||||||
CanBeDisabledIconButton(
|
|
||||||
disabled = disabled,
|
|
||||||
modifier = Modifier.size(40.dp),
|
|
||||||
imageVector = if (fullContent) {
|
|
||||||
Icons.Rounded.Article
|
|
||||||
} else {
|
|
||||||
Icons.Outlined.Article
|
|
||||||
},
|
|
||||||
contentDescription = stringResource(R.string.parse_full_content),
|
|
||||||
tint = if (fullContent) {
|
|
||||||
MaterialTheme.colorScheme.onSecondaryContainer
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.outline
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
|
||||||
val afterIsFullContent = !fullContent
|
|
||||||
fullContent = afterIsFullContent
|
|
||||||
fullContentOnClick(afterIsFullContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +1,16 @@
|
||||||
package me.ash.reader.ui.page.home.reading
|
package me.ash.reader.ui.page.home.reading
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.foundation.text.selection.DisableSelection
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.Share
|
|
||||||
import androidx.compose.material.icons.rounded.Close
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.SmallTopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import me.ash.reader.R
|
|
||||||
import me.ash.reader.data.entity.ArticleWithFeed
|
|
||||||
import me.ash.reader.ui.component.base.FeedbackIconButton
|
|
||||||
import me.ash.reader.ui.component.base.RYScaffold
|
import me.ash.reader.ui.component.base.RYScaffold
|
||||||
import me.ash.reader.ui.component.reader.reader
|
|
||||||
import me.ash.reader.ui.ext.collectAsStateValue
|
import me.ash.reader.ui.ext.collectAsStateValue
|
||||||
import me.ash.reader.ui.ext.drawVerticalScrollbar
|
import me.ash.reader.ui.ext.isScrollDown
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReadingPage(
|
fun ReadingPage(
|
||||||
|
@ -38,7 +18,8 @@ fun ReadingPage(
|
||||||
readingViewModel: ReadingViewModel = hiltViewModel(),
|
readingViewModel: ReadingViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val readingUiState = readingViewModel.readingUiState.collectAsStateValue()
|
val readingUiState = readingViewModel.readingUiState.collectAsStateValue()
|
||||||
val isScrollDown = readingUiState.listState.isScrollDown()
|
val isShowToolBar =
|
||||||
|
readingUiState.articleWithFeed != null && !readingUiState.listState.isScrollDown()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
navController.currentBackStackEntryFlow.collect {
|
navController.currentBackStackEntryFlow.collect {
|
||||||
|
@ -59,46 +40,48 @@ fun ReadingPage(
|
||||||
|
|
||||||
RYScaffold(
|
RYScaffold(
|
||||||
content = {
|
content = {
|
||||||
Box(Modifier.fillMaxSize()) {
|
Log.i("RLog", "TopBar: recomposition")
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
.fillMaxSize()
|
// Top Bar
|
||||||
.zIndex(1f),
|
TopBar(
|
||||||
contentAlignment = Alignment.TopCenter
|
isShow = isShowToolBar,
|
||||||
) {
|
title = readingUiState.articleWithFeed?.article?.title,
|
||||||
TopBar(
|
link = readingUiState.articleWithFeed?.article?.link,
|
||||||
isShow = readingUiState.articleWithFeed == null || !isScrollDown,
|
onClose = {
|
||||||
title = readingUiState.articleWithFeed?.article?.title,
|
navController.popBackStack()
|
||||||
link = readingUiState.articleWithFeed?.article?.link,
|
},
|
||||||
onClose = {
|
)
|
||||||
navController.popBackStack()
|
|
||||||
},
|
// Content
|
||||||
|
if (readingUiState.articleWithFeed != null) {
|
||||||
|
Content(
|
||||||
|
content = readingUiState.content ?: "",
|
||||||
|
feedName = readingUiState.articleWithFeed.feed.name,
|
||||||
|
title = readingUiState.articleWithFeed.article.title,
|
||||||
|
author = readingUiState.articleWithFeed.article.author,
|
||||||
|
link = readingUiState.articleWithFeed.article.link,
|
||||||
|
publishedDate = readingUiState.articleWithFeed.article.date,
|
||||||
|
isLoading = readingUiState.isLoading,
|
||||||
|
listState = readingUiState.listState,
|
||||||
|
isShowToolBar = isShowToolBar,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Content(
|
// Bottom Bar
|
||||||
content = readingUiState.content ?: "",
|
if (readingUiState.articleWithFeed != null) {
|
||||||
articleWithFeed = readingUiState.articleWithFeed,
|
|
||||||
isLoading = readingUiState.isLoading,
|
|
||||||
listState = readingUiState.listState,
|
|
||||||
isShowToolBar = readingUiState.articleWithFeed == null || !isScrollDown,
|
|
||||||
)
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.zIndex(1f),
|
|
||||||
contentAlignment = Alignment.BottomCenter
|
|
||||||
) {
|
|
||||||
BottomBar(
|
BottomBar(
|
||||||
isShow = readingUiState.articleWithFeed != null && !isScrollDown,
|
isShow = isShowToolBar,
|
||||||
articleWithFeed = readingUiState.articleWithFeed,
|
isUnread = readingUiState.articleWithFeed.article.isUnread,
|
||||||
unreadOnClick = {
|
isStarred = readingUiState.articleWithFeed.article.isStarred,
|
||||||
|
isFullContent = readingUiState.isFullContent,
|
||||||
|
onUnread = {
|
||||||
readingViewModel.markUnread(it)
|
readingViewModel.markUnread(it)
|
||||||
},
|
},
|
||||||
starredOnClick = {
|
onStarred = {
|
||||||
readingViewModel.markStarred(it)
|
readingViewModel.markStarred(it)
|
||||||
},
|
},
|
||||||
fullContentOnClick = { afterIsFullContent ->
|
onFullContent = {
|
||||||
if (afterIsFullContent) readingViewModel.renderFullContent()
|
if (it) readingViewModel.renderFullContent()
|
||||||
else readingViewModel.renderDescriptionContent()
|
else readingViewModel.renderDescriptionContent()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -107,180 +90,3 @@ fun ReadingPage(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LazyListState.isScrollDown(): Boolean {
|
|
||||||
var isScrollDown by remember { mutableStateOf(false) }
|
|
||||||
var preItemIndex by remember { mutableStateOf(0) }
|
|
||||||
var preScrollStartOffset by remember { mutableStateOf(0) }
|
|
||||||
|
|
||||||
LaunchedEffect(this) {
|
|
||||||
snapshotFlow { isScrollInProgress }.collect {
|
|
||||||
if (isScrollInProgress) {
|
|
||||||
isScrollDown = when {
|
|
||||||
firstVisibleItemIndex > preItemIndex -> true
|
|
||||||
firstVisibleItemScrollOffset < preItemIndex -> false
|
|
||||||
else -> firstVisibleItemScrollOffset > preScrollStartOffset
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
preItemIndex = firstVisibleItemIndex
|
|
||||||
preScrollStartOffset = firstVisibleItemScrollOffset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isScrollDown
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun TopBar(
|
|
||||||
isShow: Boolean,
|
|
||||||
title: String? = "",
|
|
||||||
link: String? = "",
|
|
||||||
onClose: () -> Unit = {},
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = isShow,
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
SmallTopAppBar(
|
|
||||||
modifier = Modifier.statusBarsPadding(),
|
|
||||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
|
||||||
),
|
|
||||||
title = {},
|
|
||||||
navigationIcon = {
|
|
||||||
FeedbackIconButton(
|
|
||||||
imageVector = Icons.Rounded.Close,
|
|
||||||
contentDescription = stringResource(R.string.close),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurface
|
|
||||||
) {
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
FeedbackIconButton(
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
imageVector = Icons.Outlined.Share,
|
|
||||||
contentDescription = stringResource(R.string.share),
|
|
||||||
tint = MaterialTheme.colorScheme.onSurface,
|
|
||||||
) {
|
|
||||||
context.startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply {
|
|
||||||
putExtra(
|
|
||||||
Intent.EXTRA_TEXT,
|
|
||||||
title?.takeIf { it.isNotBlank() }?.let { it + "\n" } + link
|
|
||||||
)
|
|
||||||
type = "text/plain"
|
|
||||||
}, context.getString(R.string.share)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun Content(
|
|
||||||
content: String,
|
|
||||||
articleWithFeed: ArticleWithFeed?,
|
|
||||||
listState: LazyListState,
|
|
||||||
isLoading: Boolean,
|
|
||||||
isShowToolBar: Boolean,
|
|
||||||
) {
|
|
||||||
if (articleWithFeed == null) return
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
SelectionContainer {
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.statusBarsPadding()
|
|
||||||
.run {
|
|
||||||
if (isShowToolBar) {
|
|
||||||
navigationBarsPadding()
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.drawVerticalScrollbar(listState),
|
|
||||||
state = listState,
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(64.dp))
|
|
||||||
Spacer(modifier = Modifier.height(22.dp))
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 12.dp)
|
|
||||||
) {
|
|
||||||
DisableSelection {
|
|
||||||
Header(articleWithFeed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(22.dp))
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = isLoading,
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Spacer(modifier = Modifier.height(22.dp))
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(30.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(22.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isLoading) {
|
|
||||||
reader(
|
|
||||||
context = context,
|
|
||||||
link = articleWithFeed.article.link,
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.height(128.dp))
|
|
||||||
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun BottomBar(
|
|
||||||
isShow: Boolean,
|
|
||||||
articleWithFeed: ArticleWithFeed?,
|
|
||||||
unreadOnClick: (afterIsUnread: Boolean) -> Unit = {},
|
|
||||||
starredOnClick: (afterIsStarred: Boolean) -> Unit = {},
|
|
||||||
fullContentOnClick: (afterIsFullContent: Boolean) -> Unit = {},
|
|
||||||
) {
|
|
||||||
articleWithFeed?.let {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = isShow,
|
|
||||||
enter = fadeIn() + expandVertically(),
|
|
||||||
exit = fadeOut() + shrinkVertically(),
|
|
||||||
) {
|
|
||||||
ReadingBar(
|
|
||||||
modifier = Modifier.navigationBarsPadding(),
|
|
||||||
disabled = false,
|
|
||||||
isUnread = articleWithFeed.article.isUnread,
|
|
||||||
isStarred = articleWithFeed.article.isStarred,
|
|
||||||
isFullContent = articleWithFeed.feed.isFullContent,
|
|
||||||
unreadOnClick = unreadOnClick,
|
|
||||||
starredOnClick = starredOnClick,
|
|
||||||
fullContentOnClick = fullContentOnClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,7 +28,9 @@ class ReadingViewModel @Inject constructor(
|
||||||
showLoading()
|
showLoading()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_readingUiState.update {
|
_readingUiState.update {
|
||||||
it.copy(articleWithFeed = rssRepository.get().findArticleById(articleId))
|
it.copy(
|
||||||
|
articleWithFeed = rssRepository.get().findArticleById(articleId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_readingUiState.value.articleWithFeed?.let {
|
_readingUiState.value.articleWithFeed?.let {
|
||||||
if (it.feed.isFullContent) internalRenderFullContent()
|
if (it.feed.isFullContent) internalRenderFullContent()
|
||||||
|
@ -43,6 +45,7 @@ class ReadingViewModel @Inject constructor(
|
||||||
it.copy(
|
it.copy(
|
||||||
content = it.articleWithFeed?.article?.fullContent
|
content = it.articleWithFeed?.article?.fullContent
|
||||||
?: it.articleWithFeed?.article?.rawDescription ?: "",
|
?: it.articleWithFeed?.article?.rawDescription ?: "",
|
||||||
|
isFullContent = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +56,7 @@ class ReadingViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun internalRenderFullContent() {
|
private suspend fun internalRenderFullContent() {
|
||||||
showLoading()
|
showLoading()
|
||||||
try {
|
try {
|
||||||
_readingUiState.update {
|
_readingUiState.update {
|
||||||
|
@ -61,7 +64,8 @@ class ReadingViewModel @Inject constructor(
|
||||||
content = rssHelper.parseFullContent(
|
content = rssHelper.parseFullContent(
|
||||||
_readingUiState.value.articleWithFeed?.article?.link ?: "",
|
_readingUiState.value.articleWithFeed?.article?.link ?: "",
|
||||||
_readingUiState.value.articleWithFeed?.article?.title ?: ""
|
_readingUiState.value.articleWithFeed?.article?.title ?: ""
|
||||||
)
|
),
|
||||||
|
isFullContent = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -133,6 +137,7 @@ class ReadingViewModel @Inject constructor(
|
||||||
data class ReadingUiState(
|
data class ReadingUiState(
|
||||||
val articleWithFeed: ArticleWithFeed? = null,
|
val articleWithFeed: ArticleWithFeed? = null,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
|
val isFullContent: Boolean = false,
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
val listState: LazyListState = LazyListState(),
|
val listState: LazyListState = LazyListState(),
|
||||||
)
|
)
|
|
@ -0,0 +1,72 @@
|
||||||
|
package me.ash.reader.ui.page.home.reading
|
||||||
|
|
||||||
|
import RYExtensibleVisibility
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Share
|
||||||
|
import androidx.compose.material.icons.rounded.Close
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SmallTopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import me.ash.reader.R
|
||||||
|
import me.ash.reader.ui.component.base.FeedbackIconButton
|
||||||
|
import me.ash.reader.ui.ext.share
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TopBar(
|
||||||
|
isShow: Boolean,
|
||||||
|
title: String? = "",
|
||||||
|
link: String? = "",
|
||||||
|
onClose: () -> Unit = {},
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.zIndex(1f),
|
||||||
|
contentAlignment = Alignment.TopCenter
|
||||||
|
) {
|
||||||
|
RYExtensibleVisibility(visible = isShow) {
|
||||||
|
SmallTopAppBar(
|
||||||
|
modifier = Modifier.statusBarsPadding(),
|
||||||
|
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
title = {},
|
||||||
|
navigationIcon = {
|
||||||
|
FeedbackIconButton(
|
||||||
|
imageVector = Icons.Rounded.Close,
|
||||||
|
contentDescription = stringResource(R.string.close),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface
|
||||||
|
) {
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
FeedbackIconButton(
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
imageVector = Icons.Outlined.Share,
|
||||||
|
contentDescription = stringResource(R.string.share),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
) {
|
||||||
|
context.share(title
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { it + "\n" } + link
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user