From ba3620d84f9186f9825dd3896c4c9e4364028bd1 Mon Sep 17 00:00:00 2001 From: Ash Date: Mon, 23 May 2022 20:54:06 +0800 Subject: [PATCH] Optimize the reading page --- app/build.gradle | 2 +- .../reader/ui/component/base/AnimatedPopup.kt | 8 +- .../reader/ui/component/base/DisplayText.kt | 8 +- .../reader/ui/component/base/RYAsyncImage.kt | 74 +++-- .../component/base/RYExtensibleVisibility.kt | 15 + .../{SelectionChip.kt => RYSelectionChip.kt} | 2 +- .../ash/reader/ui/component/reader/Reader.kt | 3 +- .../java/me/ash/reader/ui/ext/ColorScheme.kt | 5 +- .../java/me/ash/reader/ui/ext/ContextExt.kt | 17 ++ .../me/ash/reader/ui/ext/LazyListStateExt.kt | 33 ++- .../me/ash/reader/ui/ext/ScrollbarsExt.kt | 13 +- .../reader/ui/page/home/feeds/GroupItem.kt | 8 +- .../feeds/drawer/group/GroupOptionDrawer.kt | 14 +- .../page/home/feeds/subscribe/ResultView.kt | 14 +- .../ash/reader/ui/page/home/flow/FlowPage.kt | 21 +- .../reader/ui/page/home/reading/BottomBar.kt | 131 +++++++++ .../reader/ui/page/home/reading/Content.kt | 101 +++++++ .../ash/reader/ui/page/home/reading/Header.kt | 41 +-- .../reader/ui/page/home/reading/ReadingBar.kt | 140 --------- .../ui/page/home/reading/ReadingPage.kt | 278 +++--------------- .../ui/page/home/reading/ReadingViewModel.kt | 11 +- .../ash/reader/ui/page/home/reading/TopBar.kt | 72 +++++ 22 files changed, 525 insertions(+), 486 deletions(-) create mode 100644 app/src/main/java/me/ash/reader/ui/component/base/RYExtensibleVisibility.kt rename app/src/main/java/me/ash/reader/ui/component/base/{SelectionChip.kt => RYSelectionChip.kt} (99%) create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/reading/BottomBar.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt delete mode 100644 app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingBar.kt create mode 100644 app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt diff --git a/app/build.gradle b/app/build.gradle index 5964199..fbd5484 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,9 +166,9 @@ dependencies { // https://developer.android.com/jetpack/androidx/releases/compose-material implementation "androidx.compose.material:material:$compose" implementation "androidx.compose.material:material-icons-extended:$compose" + debugImplementation "androidx.compose.ui:ui-tooling:$compose" implementation "androidx.compose.ui:ui-tooling-preview:$compose" androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose" - debugImplementation "androidx.compose.ui:ui-tooling:$compose" // hilt implementation "androidx.hilt:hilt-work:1.0.0" diff --git a/app/src/main/java/me/ash/reader/ui/component/base/AnimatedPopup.kt b/app/src/main/java/me/ash/reader/ui/component/base/AnimatedPopup.kt index d70044e..9b873e6 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/AnimatedPopup.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/AnimatedPopup.kt @@ -1,6 +1,6 @@ package me.ash.reader.ui.component.base -import androidx.compose.animation.* +import RYExtensibleVisibility import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.statusBars import androidx.compose.runtime.Composable @@ -38,11 +38,7 @@ fun AnimatedPopup( } }, ) { - AnimatedVisibility( - visible = visible, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { + RYExtensibleVisibility(visible = visible) { content() } } diff --git a/app/src/main/java/me/ash/reader/ui/component/base/DisplayText.kt b/app/src/main/java/me/ash/reader/ui/component/base/DisplayText.kt index 965b22d..56ee1a5 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/DisplayText.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/DisplayText.kt @@ -1,6 +1,6 @@ package me.ash.reader.ui.component.base -import androidx.compose.animation.* +import RYExtensibleVisibility import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -41,11 +41,7 @@ fun DisplayText( maxLines = 1, overflow = TextOverflow.Ellipsis, ) - AnimatedVisibility( - visible = desc.isNotEmpty(), - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { + RYExtensibleVisibility(visible = desc.isNotEmpty()) { Text( modifier = Modifier.height(16.dp), text = desc, diff --git a/app/src/main/java/me/ash/reader/ui/component/base/RYAsyncImage.kt b/app/src/main/java/me/ash/reader/ui/component/base/RYAsyncImage.kt index c33cd0c..bd86c62 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/RYAsyncImage.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/RYAsyncImage.kt @@ -1,7 +1,7 @@ package me.ash.reader.ui.component.base import androidx.annotation.DrawableRes -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable 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.painter.Painter import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import coil.compose.LocalImageLoader -import coil.request.ImageRequest +import coil.compose.rememberImagePainter import coil.size.Precision import coil.size.Scale import coil.size.Size @@ -33,34 +30,51 @@ fun RYAsyncImage( @DrawableRes placeholder: Int? = R.drawable.ic_hourglass_empty_black_24dp, @DrawableRes error: Int? = R.drawable.ic_broken_image_black_24dp, ) { - coil.compose.AsyncImage( - modifier = modifier, - model = ImageRequest - .Builder(LocalContext.current) - .data(data) - .crossfade(true) - .scale(scale) - .precision(precision) - .size(size) - .build(), + Image( + painter = rememberImagePainter( + data = data, + builder = { + if (placeholder != null) placeholder(placeholder) + if (error != null) error(error) + crossfade(true) + scale(scale) + precision(precision) + size(size) + }, + ), 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, - ) - }, + modifier = modifier, ) + +// 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 diff --git a/app/src/main/java/me/ash/reader/ui/component/base/RYExtensibleVisibility.kt b/app/src/main/java/me/ash/reader/ui/component/base/RYExtensibleVisibility.kt new file mode 100644 index 0000000..b1485a5 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/component/base/RYExtensibleVisibility.kt @@ -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, + ) +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/component/base/SelectionChip.kt b/app/src/main/java/me/ash/reader/ui/component/base/RYSelectionChip.kt similarity index 99% rename from app/src/main/java/me/ash/reader/ui/component/base/SelectionChip.kt rename to app/src/main/java/me/ash/reader/ui/component/base/RYSelectionChip.kt index dc9769b..68b14a9 100644 --- a/app/src/main/java/me/ash/reader/ui/component/base/SelectionChip.kt +++ b/app/src/main/java/me/ash/reader/ui/component/base/RYSelectionChip.kt @@ -26,7 +26,7 @@ import me.ash.reader.ui.theme.palette.alwaysLight @OptIn(ExperimentalMaterialApi::class) @Composable -fun SelectionChip( +fun RYSelectionChip( content: String, selected: Boolean, modifier: Modifier = Modifier, diff --git a/app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt b/app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt index 0a47d3a..464369b 100644 --- a/app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt +++ b/app/src/main/java/me/ash/reader/ui/component/reader/Reader.kt @@ -27,7 +27,8 @@ import android.util.Log import androidx.compose.foundation.lazy.LazyListScope import me.ash.reader.R -fun LazyListScope.reader( +@Suppress("FunctionName") +fun LazyListScope.Reader( context: Context, link: String, content: String, diff --git a/app/src/main/java/me/ash/reader/ui/ext/ColorScheme.kt b/app/src/main/java/me/ash/reader/ui/ext/ColorScheme.kt index 1e2276f..a4c388d 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/ColorScheme.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/ColorScheme.kt @@ -1,16 +1,19 @@ package me.ash.reader.ui.ext 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.compositeOver import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import kotlin.math.ln +@Composable fun ColorScheme.surfaceColorAtElevation( elevation: Dp, color: Color = surface, -): Color = color.atElevation(surfaceTint, elevation) +): Color = remember(this, elevation, color) { color.atElevation(surfaceTint, elevation) } fun Color.atElevation( sourceColor: Color, diff --git a/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt b/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt index 2db93f0..3c63125 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/ContextExt.kt @@ -4,9 +4,11 @@ import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.content.Intent +import android.net.Uri import android.util.Log import android.widget.Toast import androidx.core.content.FileProvider +import me.ash.reader.R import me.ash.reader.data.model.Version import me.ash.reader.data.model.toVersion import java.io.File @@ -53,4 +55,19 @@ fun Context.showToast(message: String?, duration: Int = Toast.LENGTH_SHORT) { fun Context.showToastLong(message: String?) { 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))) } } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/ext/LazyListStateExt.kt b/app/src/main/java/me/ash/reader/ui/ext/LazyListStateExt.kt index ca9eb77..dccea7c 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/LazyListStateExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/LazyListStateExt.kt @@ -1,8 +1,7 @@ package me.ash.reader.ui.ext import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.paging.compose.LazyPagingItems import kotlin.math.abs @@ -27,4 +26,34 @@ fun LazyPagingItems.rememberLazyListState(): LazyListState { // Return rememberLazyListState (normal case). 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 } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt b/app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt index ce85a11..d7c7184 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/ScrollbarsExt.kt @@ -52,6 +52,7 @@ 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.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color @@ -173,11 +174,15 @@ private fun CacheDrawScope.onDrawScrollbar( return { if (showScrollbar) { - drawRect( + drawRoundRect( color = color, topLeft = topLeft, 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) } LaunchedEffect(scrolled, alpha) { scrolled.collectLatest { - alpha.snapTo(1f) + alpha.snapTo(0.3f) delay(ViewConfiguration.getScrollDefaultDelay().toLong()) alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) } @@ -231,7 +236,7 @@ private fun Modifier.drawScrollbar( // 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) + val color = MaterialTheme.colorScheme.onSurfaceVariant Modifier .nestedScroll(nestedScrollConnection) .drawWithCache { diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt index 65e1ab6..61f79d5 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/GroupItem.kt @@ -1,7 +1,7 @@ package me.ash.reader.ui.page.home.feeds +import RYExtensibleVisibility import android.view.HapticFeedbackConstants -import androidx.compose.animation.* import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable @@ -104,11 +104,7 @@ fun GroupItem( } } Spacer(modifier = Modifier.height(22.dp)) - AnimatedVisibility( - visible = expanded, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { + RYExtensibleVisibility(visible = expanded) { Column { feeds.forEach { feed -> FeedItem( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt index d153ee7..b64d5aa 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/drawer/group/GroupOptionDrawer.kt @@ -34,7 +34,7 @@ import kotlinx.coroutines.launch import me.ash.reader.R import me.ash.reader.data.entity.Group 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.TextFieldDialog import me.ash.reader.ui.ext.* @@ -162,7 +162,7 @@ private fun Preset( crossAxisSpacing = 10.dp, mainAxisSpacing = 10.dp, ) { - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.allow_notification), selected = false, @@ -178,7 +178,7 @@ private fun Preset( ) { groupOptionViewModel.showAllAllowNotificationDialog() } - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.parse_full_content), selected = false, @@ -194,7 +194,7 @@ private fun Preset( ) { groupOptionViewModel.showAllParseFullContentDialog() } - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.clear_articles), selected = false, @@ -202,7 +202,7 @@ private fun Preset( groupOptionViewModel.showClearDialog() } if (group?.id != context.currentAccountId.getDefaultGroupId()) { - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.delete_group), selected = false, @@ -227,7 +227,7 @@ private fun FlowRowGroups( ) { groupOptionUiState.groups.forEach { if (it.id != group?.id) { - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = it.name, selected = false, @@ -248,7 +248,7 @@ private fun LazyRowGroups( LazyRow { items(groupOptionUiState.groups) { if (it.id != group?.id) { - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = it.name, selected = false, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt index f6d6dce..692101e 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt @@ -30,7 +30,7 @@ import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.MainAxisAlignment import me.ash.reader.R 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.ext.roundClick import me.ash.reader.ui.theme.palette.alwaysLight @@ -127,7 +127,7 @@ private fun Preset( crossAxisSpacing = 10.dp, mainAxisSpacing = 10.dp, ) { - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.allow_notification), selected = selectedAllowNotificationPreset, @@ -144,7 +144,7 @@ private fun Preset( ) { allowNotificationPresetOnClick() } - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.parse_full_content), selected = selectedParseFullContentPreset, @@ -162,14 +162,14 @@ private fun Preset( parseFullContentPresetOnClick() } if (showUnsubscribe) { - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.clear_articles), selected = false, ) { clearArticlesOnClick() } - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = stringResource(R.string.unsubscribe), selected = false, @@ -196,7 +196,7 @@ private fun AddToGroup( verticalAlignment = Alignment.CenterVertically, ) { items(groups) { - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = it.name, selected = it.id == selectedGroupId, @@ -215,7 +215,7 @@ private fun AddToGroup( mainAxisSpacing = 10.dp, ) { groups.forEach { - SelectionChip( + RYSelectionChip( modifier = Modifier.animateContentSize(), content = it.name, selected = it.id == selectedGroupId, diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt index 3fba040..2b243ea 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt @@ -1,7 +1,7 @@ package me.ash.reader.ui.page.home.flow +import RYExtensibleVisibility import androidx.activity.compose.BackHandler -import androidx.compose.animation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -19,7 +19,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController -import androidx.paging.LoadState import androidx.paging.compose.collectAsLazyPagingItems import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -121,11 +120,7 @@ fun FlowPage( } }, actions = { - AnimatedVisibility( - visible = !filterUiState.filter.isStarred(), - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { + RYExtensibleVisibility(visible = !filterUiState.filter.isStarred()) { FeedbackIconButton( imageVector = Icons.Rounded.DoneAll, contentDescription = stringResource(R.string.mark_all_as_read), @@ -171,11 +166,7 @@ fun FlowPage( ) { item { DisplayTextHeader(filterUiState, isSyncing, articleListFeedIcon.value) - AnimatedVisibility( - visible = markAsRead, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { + RYExtensibleVisibility(visible = markAsRead) { Spacer(modifier = Modifier.height((56 + 24 + 10).dp)) } MarkAsReadBar( @@ -193,11 +184,7 @@ fun FlowPage( markAsReadBefore = it, ) } - AnimatedVisibility( - visible = onSearch, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { + RYExtensibleVisibility(visible = onSearch) { SearchBar( value = homeUiState.searchContent, placeholder = when { diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/BottomBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/BottomBar.kt new file mode 100644 index 0000000..0dbead5 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/BottomBar.kt @@ -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) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt new file mode 100644 index 0000000..b1883ad --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/Content.kt @@ -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)) + } + } + } +} diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt index 40f2552..b50823a 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/Header.kt @@ -1,62 +1,67 @@ package me.ash.reader.ui.page.home.reading -import android.content.Intent -import android.net.Uri import androidx.compose.foundation.layout.* import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.platform.LocalContext 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.openURL import me.ash.reader.ui.ext.roundClick +import java.util.* @Composable fun Header( - articleWithFeed: ArticleWithFeed, + feedName: String, + title: String, + author: String? = null, + link: String? = null, + publishedDate: Date, ) { val context = LocalContext.current + val dateString = remember(publishedDate) { + publishedDate.formatAsString(context, atHourMinute = true) + } Column( modifier = Modifier .fillMaxWidth() .roundClick { - articleWithFeed.article.link.let { - if (it.isNotEmpty()) { - context.startActivity( - Intent(Intent.ACTION_VIEW, Uri.parse(articleWithFeed.article.link)) - ) - } - } + context.openURL(link) } .padding(12.dp) ) { Text( - text = articleWithFeed.article.date.formatAsString(context, atHourMinute = true), - color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), + modifier = Modifier.alpha(0.7f), + text = dateString, + color = MaterialTheme.colorScheme.outline, style = MaterialTheme.typography.labelMedium, ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = articleWithFeed.article.title, + text = title, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.headlineLarge, ) Spacer(modifier = Modifier.height(4.dp)) - articleWithFeed.article.author?.let { + author?.let { if (it.isNotEmpty()) { Text( + modifier = Modifier.alpha(0.7f), text = it, - color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), + color = MaterialTheme.colorScheme.outline, style = MaterialTheme.typography.labelMedium, ) } } Text( - text = articleWithFeed.feed.name, - color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), + modifier = Modifier.alpha(0.7f), + text = feedName, + color = MaterialTheme.colorScheme.outline, style = MaterialTheme.typography.labelMedium, ) } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingBar.kt deleted file mode 100644 index a5af7e8..0000000 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingBar.kt +++ /dev/null @@ -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) - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt index f6fef8c..d198734 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingPage.kt @@ -1,36 +1,16 @@ package me.ash.reader.ui.page.home.reading -import android.content.Intent import android.util.Log -import androidx.compose.animation.* -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.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.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect 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.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.reader.reader import me.ash.reader.ui.ext.collectAsStateValue -import me.ash.reader.ui.ext.drawVerticalScrollbar +import me.ash.reader.ui.ext.isScrollDown @Composable fun ReadingPage( @@ -38,7 +18,8 @@ fun ReadingPage( readingViewModel: ReadingViewModel = hiltViewModel(), ) { val readingUiState = readingViewModel.readingUiState.collectAsStateValue() - val isScrollDown = readingUiState.listState.isScrollDown() + val isShowToolBar = + readingUiState.articleWithFeed != null && !readingUiState.listState.isScrollDown() LaunchedEffect(Unit) { navController.currentBackStackEntryFlow.collect { @@ -59,46 +40,48 @@ fun ReadingPage( RYScaffold( content = { - Box(Modifier.fillMaxSize()) { - Box( - modifier = Modifier - .fillMaxSize() - .zIndex(1f), - contentAlignment = Alignment.TopCenter - ) { - TopBar( - isShow = readingUiState.articleWithFeed == null || !isScrollDown, - title = readingUiState.articleWithFeed?.article?.title, - link = readingUiState.articleWithFeed?.article?.link, - onClose = { - navController.popBackStack() - }, + Log.i("RLog", "TopBar: recomposition") + + Box(modifier = Modifier.fillMaxSize()) { + // Top Bar + TopBar( + isShow = isShowToolBar, + title = readingUiState.articleWithFeed?.article?.title, + 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( - content = readingUiState.content ?: "", - articleWithFeed = readingUiState.articleWithFeed, - isLoading = readingUiState.isLoading, - listState = readingUiState.listState, - isShowToolBar = readingUiState.articleWithFeed == null || !isScrollDown, - ) - Box( - modifier = Modifier - .fillMaxSize() - .zIndex(1f), - contentAlignment = Alignment.BottomCenter - ) { + // Bottom Bar + if (readingUiState.articleWithFeed != null) { BottomBar( - isShow = readingUiState.articleWithFeed != null && !isScrollDown, - articleWithFeed = readingUiState.articleWithFeed, - unreadOnClick = { + isShow = isShowToolBar, + isUnread = readingUiState.articleWithFeed.article.isUnread, + isStarred = readingUiState.articleWithFeed.article.isStarred, + isFullContent = readingUiState.isFullContent, + onUnread = { readingViewModel.markUnread(it) }, - starredOnClick = { + onStarred = { readingViewModel.markStarred(it) }, - fullContentOnClick = { afterIsFullContent -> - if (afterIsFullContent) readingViewModel.renderFullContent() + onFullContent = { + if (it) readingViewModel.renderFullContent() 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, - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt index 4a345d3..e2ca168 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/ReadingViewModel.kt @@ -28,7 +28,9 @@ class ReadingViewModel @Inject constructor( showLoading() viewModelScope.launch { _readingUiState.update { - it.copy(articleWithFeed = rssRepository.get().findArticleById(articleId)) + it.copy( + articleWithFeed = rssRepository.get().findArticleById(articleId) + ) } _readingUiState.value.articleWithFeed?.let { if (it.feed.isFullContent) internalRenderFullContent() @@ -43,6 +45,7 @@ class ReadingViewModel @Inject constructor( it.copy( content = it.articleWithFeed?.article?.fullContent ?: it.articleWithFeed?.article?.rawDescription ?: "", + isFullContent = false ) } } @@ -53,7 +56,7 @@ class ReadingViewModel @Inject constructor( } } - suspend fun internalRenderFullContent() { + private suspend fun internalRenderFullContent() { showLoading() try { _readingUiState.update { @@ -61,7 +64,8 @@ class ReadingViewModel @Inject constructor( content = rssHelper.parseFullContent( _readingUiState.value.articleWithFeed?.article?.link ?: "", _readingUiState.value.articleWithFeed?.article?.title ?: "" - ) + ), + isFullContent = true ) } } catch (e: Exception) { @@ -133,6 +137,7 @@ class ReadingViewModel @Inject constructor( data class ReadingUiState( val articleWithFeed: ArticleWithFeed? = null, val content: String? = null, + val isFullContent: Boolean = false, val isLoading: Boolean = true, val listState: LazyListState = LazyListState(), ) \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt b/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt new file mode 100644 index 0000000..4d88925 --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/home/reading/TopBar.kt @@ -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 + ) + } + } + ) + } + } +} \ No newline at end of file