From 640406b694e466713541e2460564513e5a034d32 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 17:49:21 +0800 Subject: [PATCH 01/12] Update FeedsPage.kt --- .../java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index 94ef8d916..bdc6699f9 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -1,11 +1,13 @@ package me.ash.reader.ui.page.home.feeds +import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.animation.core.* import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.KeyboardArrowRight import androidx.compose.material.icons.outlined.Settings @@ -81,6 +83,7 @@ fun FeedsPage( val newVersion = LocalNewVersionNumber.current val skipVersion = LocalSkipVersionNumber.current val currentVersion = remember { context.getCurrentVersion() } + val listState = if (groupWithFeedList.isNotEmpty()) feedsUiState.listState else rememberLazyListState() val owner = LocalLifecycleOwner.current var isSyncing by remember { mutableStateOf(false) } @@ -157,7 +160,9 @@ fun FeedsPage( } }, content = { - LazyColumn { + LazyColumn ( + state = listState + ) { item { DisplayText( modifier = Modifier From 8dd1899b88592561c9cccc639769af07325ac294 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 17:55:52 +0800 Subject: [PATCH 02/12] Update ArticleItem.kt --- .../main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt index bd800bd77..d97f6cc63 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleItem.kt @@ -164,16 +164,17 @@ fun ArticleItem( @Composable fun SwipeableArticleItem( articleWithFeed: ArticleWithFeed, + isFilterUnread: Boolean, onClick: (ArticleWithFeed) -> Unit = {}, onSwipeOut: (ArticleWithFeed) -> Unit = {}, ) { var isArticleVisible by remember { mutableStateOf(true) } val dismissState = rememberDismissState(initialValue = DismissValue.Default, confirmStateChange = { if (it == DismissValue.DismissedToEnd) { - isArticleVisible = false + isArticleVisible = !isFilterUnread onSwipeOut(articleWithFeed) } - true + isFilterUnread }) if (isArticleVisible) { SwipeToDismiss( From 1da288c76b656274b3be61d24b138d45743ba87c Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:06:29 +0800 Subject: [PATCH 03/12] Update ArticleList.kt --- .../main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt index 0b1d3fde9..48f8228ca 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt @@ -15,6 +15,7 @@ import me.ash.reader.domain.model.article.ArticleWithFeed @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class) fun LazyListScope.ArticleList( pagingItems: LazyPagingItems, + isFilterUnread: Boolean, isShowFeedIcon: Boolean, isShowStickyHeader: Boolean, articleListTonalElevation: Int, @@ -25,8 +26,9 @@ fun LazyListScope.ArticleList( when (val item = pagingItems.peek(index)) { is ArticleFlowItem.Article -> { item(key = item.articleWithFeed.article.id) { - SwipeableArticleItem( + swipeToDismiss( articleWithFeed = item.articleWithFeed, + isFilterUnread = isFilterUnread, onClick = { onClick(it) }, onSwipeOut = { onSwipeOut(it) } ) From 443504135b00dd63a6cabc69df333b38bcdd9cd7 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:08:51 +0800 Subject: [PATCH 04/12] Update FlowPage.kt --- app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt | 3 +++ 1 file changed, 3 insertions(+) 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 50dc5ee90..d55da6098 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 @@ -23,6 +23,8 @@ import androidx.work.WorkInfo import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.ash.reader.R +import me.ash.reader.domain.model.article.ArticleFlowItem +import me.ash.reader.domain.model.general.Filter import me.ash.reader.domain.model.general.MarkAsReadConditions import me.ash.reader.infrastructure.preference.* import me.ash.reader.ui.component.FilterBar @@ -232,6 +234,7 @@ fun FlowPage( } ArticleList( pagingItems = pagingItems, + isFilterUnread = filterUiState.filter == Filter.Unread, isShowFeedIcon = articleListFeedIcon.value, isShowStickyHeader = articleListDateStickyHeader.value, articleListTonalElevation = articleListTonalElevation.value, From 7ebd562322dd70d2eca61d80687926b83637fe06 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:11:14 +0800 Subject: [PATCH 05/12] Update FlowPage.kt --- app/src/main/java/me/ash/reader/ui/page/home/flow/FlowPage.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d55da6098..243eecd56 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 @@ -246,8 +246,8 @@ fun FlowPage( } ) { flowViewModel.markAsRead( - groupId = filterUiState.group?.id, - feedId = filterUiState.feed?.id, + groupId = null, + feedId = null, articleId = it.article.id, MarkAsReadConditions.All ) From 805700f264a2c855a9c59d5a151ba8abf3be87e1 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:19:11 +0800 Subject: [PATCH 06/12] Update ModifierExt.kt --- .../java/me/ash/reader/ui/ext/ModifierExt.kt | 183 +++++++++++++++++- 1 file changed, 182 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/ash/reader/ui/ext/ModifierExt.kt b/app/src/main/java/me/ash/reader/ui/ext/ModifierExt.kt index 14931de55..18aa79676 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/ModifierExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/ModifierExt.kt @@ -3,25 +3,45 @@ package me.ash.reader.ui.ext import android.annotation.SuppressLint import android.view.HapticFeedbackConstants import android.view.SoundEffectConstants +import androidx.compose.animation.core.snap import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.FractionalThreshold +import androidx.compose.material.rememberSwipeableState +import androidx.compose.material.swipeable import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.composed import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.PagerScope import com.google.accompanist.pager.calculateCurrentOffsetForPage +import kotlinx.coroutines.launch import kotlin.math.absoluteValue @OptIn(ExperimentalPagerApi::class) @@ -126,4 +146,165 @@ fun Modifier.combinedFeedbackClickable( }, ) } -} \ No newline at end of file +} + +enum class ArticleSwipeDirection(val raw: Int) { + Up(1), + Right(2), + Down(3), + Left(4), + Default(0) +} + +@OptIn(ExperimentalMaterialApi::class) +fun Modifier.swipeLeftAndRight(onLeft: () -> Unit, onRight: () -> Unit): Modifier = composed { + var screenWidth by rememberSaveable { mutableStateOf(0f) } + var screenHeight by rememberSaveable { mutableStateOf(0f) } + val swipeableState = rememberSwipeableState( + ArticleSwipeDirection.Default, + animationSpec = snap() + ) + val connection = remember { + object: NestedScrollConnection { + // Let the children eat first, we consume nothing + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { return Offset.Zero } + + // Let it scroll... + override suspend fun onPreFling(available: Velocity): Velocity { return Velocity.Zero } + + // We consume the rest + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource, + ): Offset { + // use leftover delta to swipe parent + return Offset(0f, swipeableState.performDrag(available.x)) + } + + // We fling but with zero speed (needed to trigger the event, but too fast will overshoot) + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + // perform fling on parent to trigger state change + swipeableState.performFling(velocity = 0f) + return available + } + } + } + val anchorWeight = remember(screenWidth) { + if (screenWidth == 0f) { + 1f + } else { + screenWidth + } + } + val scope = rememberCoroutineScope() + if (swipeableState.isAnimationRunning) { + DisposableEffect(Unit) { + onDispose { + when (swipeableState.currentValue) { + ArticleSwipeDirection.Left -> { + onLeft() + } + ArticleSwipeDirection.Right -> { + onRight() + } + else -> { + return@onDispose + } + } + scope.launch { + swipeableState.snapTo(ArticleSwipeDirection.Default) + } + } + } + } + return@composed Modifier + .onSizeChanged { screenWidth = it.width.toFloat() } + .nestedScroll(connection) + .swipeable( + state = swipeableState, + anchors = mapOf( + 0f to ArticleSwipeDirection.Right, + anchorWeight / 2 to ArticleSwipeDirection.Default, + anchorWeight to ArticleSwipeDirection.Left, + ), + thresholds = { _, _ -> FractionalThreshold(0.3f) }, + orientation = Orientation.Horizontal, + ) +} + +@OptIn(ExperimentalMaterialApi::class) +fun Modifier.swipeableUpDown(onUp: () -> Unit, onDown: () -> Unit): Modifier = composed { + var screenHeight by rememberSaveable { mutableStateOf(0f) } + val swipeableState = rememberSwipeableState( + ArticleSwipeDirection.Default, + animationSpec = snap() + ) + val connection = remember { + object: NestedScrollConnection { + // Let the children eat first, we consume nothing + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { return Offset.Zero } + + // Let it scroll... + override suspend fun onPreFling(available: Velocity): Velocity { return Velocity.Zero } + + // We consume the rest + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource, + ): Offset { + // use leftover delta to swipe parent + return Offset(0f, swipeableState.performDrag(available.y)) + } + + // We fling but with zero speed (needed to trigger the event, but too fast will overshoot) + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + // perform fling on parent to trigger state change + swipeableState.performFling(velocity = 0f) + return available + } + } + } + val anchorHeight = remember(screenHeight) { + if (screenHeight == 0f) { + 1f + } else { + screenHeight + } + } + val scope = rememberCoroutineScope() + if (swipeableState.isAnimationRunning) { + DisposableEffect(Unit) { + onDispose { + when (swipeableState.currentValue) { + ArticleSwipeDirection.Up -> { + onUp() + } + ArticleSwipeDirection.Down -> { + onDown() + } + else -> { + return@onDispose + } + } + scope.launch { + swipeableState.snapTo(ArticleSwipeDirection.Default) + } + } + } + } + return@composed Modifier + .onSizeChanged { screenHeight = it.height.toFloat() } + .nestedScroll(connection) + .swipeable( + state = swipeableState, + anchors = mapOf( + 0f to ArticleSwipeDirection.Up, + anchorHeight / 2 to ArticleSwipeDirection.Default, + anchorHeight to ArticleSwipeDirection.Down, + ), + thresholds = { _, _ -> FractionalThreshold(0.3f) }, + orientation = Orientation.Vertical, + ) +} From 73eda4bdfa38d60ab8cc778cd164de5283268ae1 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:25:55 +0800 Subject: [PATCH 07/12] Update HomeViewModel.kt --- .../ash/reader/ui/page/home/HomeViewModel.kt | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt index 83ac2b61a..139817d2f 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/HomeViewModel.kt @@ -41,6 +41,9 @@ class HomeViewModel @Inject constructor( private val _filterUiState = MutableStateFlow(FilterState()) val filterUiState = _filterUiState.asStateFlow() + private val _readingProgressState = MutableStateFlow(ReadingProgressState()) + val readingProgressState = _readingProgressState.asStateFlow() + val syncWorkLiveData = workManager.getWorkInfosByTagLiveData(SyncWorker.WORK_NAME) fun sync() { @@ -57,9 +60,57 @@ class HomeViewModel @Inject constructor( filter = filterState.filter, ) } + resetReadingProgress() fetchArticles() } + fun resetReadingProgress() { + _readingProgressState.update { + it.copy( + readingCurrentItemNumber = -1, + readingId = "", + readingCurrentNumber = -1, + readingList = emptyList(), + readingNext = "", + readingPrev = "", + ) + } + } + + fun changeReadingProgressListAndItemNum(list: List, itemNum: Int) { + _readingProgressState.update { + it.copy( + readingList = list, + readingCurrentItemNumber = itemNum + ) + } + } + + fun recordReadingState(currentNumber: Int, + previous: String, + current: String, + next: String) { + _readingProgressState.update { + it.copy( + readingCurrentNumber = currentNumber, + readingPrev = previous, + readingNext = next, + readingId = current + ) + } + } + + fun changeReadingCurrentId(id: String) { + _readingProgressState.update { + it.copy( + readingId = id, + readingCurrentItemNumber = -1, + readingPrev = "", + readingNext = "" + ) + } + } + fun fetchArticles() { _homeUiState.update { it.copy( @@ -108,3 +159,14 @@ data class HomeUiState( val pagingData: Flow> = emptyFlow(), val searchContent: String = "", ) + +data class ReadingProgressState( + // for flow page scroll usage + val readingCurrentItemNumber: Int = -1, + + val readingId: String = "", + val readingCurrentNumber: Int = -1, + val readingList: List = emptyList(), + val readingNext: String = "", + val readingPrev: String = "", +) From 27aa3ef04ae56b9ef23387529acd47ad7ff009f1 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:30:20 +0800 Subject: [PATCH 08/12] Update ArticleList.kt --- .../reader/ui/page/home/flow/ArticleList.kt | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt index 48f8228ca..05b51c474 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/flow/ArticleList.kt @@ -15,17 +15,28 @@ import me.ash.reader.domain.model.article.ArticleWithFeed @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class) fun LazyListScope.ArticleList( pagingItems: LazyPagingItems, + currentReadingId: String, isFilterUnread: Boolean, isShowFeedIcon: Boolean, isShowStickyHeader: Boolean, articleListTonalElevation: Int, onClick: (ArticleWithFeed) -> Unit = {}, - onSwipeOut: (ArticleWithFeed) -> Unit = {} + onSwipeOut: (ArticleWithFeed) -> Unit = {}, + onFinished: (List, Int) -> Unit = { strings: List, i: Int -> } ) { + var count =0 + var matched = -1 + val idList = mutableListOf() for (index in 0 until pagingItems.itemCount) { when (val item = pagingItems.peek(index)) { is ArticleFlowItem.Article -> { - item(key = item.articleWithFeed.article.id) { + val id = item.articleWithFeed.article.id + idList.add(id) + if (matched < 0 && id == currentReadingId) { + matched = count + } + count++ + item(key = id) { swipeToDismiss( articleWithFeed = item.articleWithFeed, isFilterUnread = isFilterUnread, @@ -36,7 +47,11 @@ fun LazyListScope.ArticleList( } is ArticleFlowItem.Date -> { - if (item.showSpacer) item { Spacer(modifier = Modifier.height(40.dp)) } + if (item.showSpacer) { + count++ + item { Spacer(modifier = Modifier.height(40.dp)) } + } + count++ if (isShowStickyHeader) { stickyHeader(key = item.date) { StickyHeader(item.date, isShowFeedIcon, articleListTonalElevation) @@ -52,3 +67,4 @@ fun LazyListScope.ArticleList( } } } +onFinished(idList, matched) From 2e517db206162816c4d20db767d181b07f5d7e9d Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:35:55 +0800 Subject: [PATCH 09/12] Update FlowPage.kt --- .../ash/reader/ui/page/home/flow/FlowPage.kt | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) 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 243eecd56..0342b33b3 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 @@ -23,7 +23,6 @@ import androidx.work.WorkInfo import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.ash.reader.R -import me.ash.reader.domain.model.article.ArticleFlowItem import me.ash.reader.domain.model.general.Filter import me.ash.reader.domain.model.general.MarkAsReadConditions import me.ash.reader.infrastructure.preference.* @@ -57,6 +56,7 @@ fun FlowPage( val flowUiState = flowViewModel.flowUiState.collectAsStateValue() val filterUiState = homeViewModel.filterUiState.collectAsStateValue() val pagingItems = homeUiState.pagingData.collectAsLazyPagingItems() + val readingProcessState = homeViewModel.readingProgressState.collectAsStateValue() val listState = if (pagingItems.itemCount > 0) flowUiState.listState else rememberLazyListState() @@ -93,6 +93,17 @@ fun FlowPage( } } + LaunchedEffect(pagingItems.itemCount) { + val id = readingProcessState.readingCurrentItemNumber + if (id >= 0) { + scope.launch { + flowUiState.listState.scrollToItem(id) + homeViewModel.changeReadingCurrentId("") + + } + } + } + BackHandler(onSearch) { onSearch = false } @@ -109,7 +120,7 @@ fun FlowPage( onSearch = false if (navController.previousBackStackEntry == null) { navController.navigate(RouteName.FEEDS) { - launchSingleTop = true + launchSingleTop = false } } else { navController.popBackStack() @@ -234,6 +245,7 @@ fun FlowPage( } ArticleList( pagingItems = pagingItems, + currentReadingId = homeViewModel.readingProgressState.value.readingId, isFilterUnread = filterUiState.filter == Filter.Unread, isShowFeedIcon = articleListFeedIcon.value, isShowStickyHeader = articleListDateStickyHeader.value, @@ -243,14 +255,19 @@ fun FlowPage( navController.navigate("${RouteName.READING}/${it.article.id}") { launchSingleTop = true } - } - ) { + }, + onSwipeOut = { flowViewModel.markAsRead( groupId = null, feedId = null, articleId = it.article.id, MarkAsReadConditions.All ) + }) { + list, curItemNum -> + if (navController.currentDestination?.route == RouteName.FLOW) { + homeViewModel.changeReadingProgressListAndItemNum(list, curItemNum) + } } item { Spacer(modifier = Modifier.height(128.dp)) From ca88c8e0314231f3bf248fb5446fda020ca12896 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:03:32 +0800 Subject: [PATCH 10/12] Update BottomBar.kt --- .../reader/ui/page/home/reading/BottomBar.kt | 26 ++++--------------- 1 file changed, 5 insertions(+), 21 deletions(-) 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 index 3272ec402..850aef325 100644 --- 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 @@ -6,13 +6,13 @@ 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.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,8 +33,8 @@ fun BottomBar( isFullContent: Boolean, onUnread: (isUnread: Boolean) -> Unit = {}, onStarred: (isStarred: Boolean) -> Unit = {}, - onNextArticle: () -> Unit = {}, onFullContent: (isFullContent: Boolean) -> Unit = {}, + progress: String, ) { val tonalElevation = LocalReadingPageTonalElevation.current @@ -59,6 +59,9 @@ fun BottomBar( horizontalArrangement = Arrangement.SpaceAround, verticalAlignment = Alignment.CenterVertically, ) { + Text( + text = progress + ) CanBeDisabledIconButton( modifier = Modifier.size(40.dp), disabled = false, @@ -95,25 +98,6 @@ fun BottomBar( view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) onStarred(!isStarred) } - CanBeDisabledIconButton( - disabled = false, - modifier = Modifier.size(40.dp), - imageVector = Icons.Rounded.ExpandMore, - contentDescription = "Next Article", - tint = MaterialTheme.colorScheme.outline, - ) { - view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) - onNextArticle() - } - 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), From 7a59fe4eb7b40501267fa905f8f643992d8dd22b Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:13:05 +0800 Subject: [PATCH 11/12] Update ReadingPage.kt --- .../ui/page/home/reading/ReadingPage.kt | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) 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 66f791efb..6ea8e0062 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 @@ -8,7 +8,10 @@ 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.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController @@ -16,8 +19,11 @@ import androidx.paging.compose.collectAsLazyPagingItems import me.ash.reader.infrastructure.preference.LocalReadingAutoHideToolbar import me.ash.reader.infrastructure.preference.LocalReadingPageTonalElevation import me.ash.reader.ui.component.base.RYScaffold +import me.ash.reader.ui.ext.ArticleSwipeDirection import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.isScrollDown +import me.ash.reader.ui.ext.swipeLeftAndRight +import me.ash.reader.ui.ext.swipeableUpDown import me.ash.reader.ui.page.home.HomeViewModel @OptIn(ExperimentalAnimationApi::class) @@ -27,17 +33,20 @@ fun ReadingPage( homeViewModel: HomeViewModel, readingViewModel: ReadingViewModel = hiltViewModel(), ) { + val context = LocalContext.current val tonalElevation = LocalReadingPageTonalElevation.current val readingUiState = readingViewModel.readingUiState.collectAsStateValue() val homeUiState = homeViewModel.homeUiState.collectAsStateValue() + val readingProgressState = homeViewModel.readingProgressState.collectAsStateValue() + val slideDirection = remember { mutableStateOf(ArticleSwipeDirection.Default.raw) } + val isShowToolBar = if (LocalReadingAutoHideToolbar.current.value) { readingUiState.articleWithFeed != null && !readingUiState.listState.isScrollDown() } else { true } - val pagingItems = homeUiState.pagingData.collectAsLazyPagingItems().itemSnapshotList - readingViewModel.recorderNextArticle(pagingItems) + readingViewModel.recordCurrentReadingState(homeViewModel) LaunchedEffect(Unit) { navController.currentBackStackEntryFlow.collect { @@ -64,7 +73,29 @@ fun ReadingPage( content = { Log.i("RLog", "TopBar: recomposition") - Box(modifier = Modifier.fillMaxSize()) { + Box(modifier = Modifier + .fillMaxSize() + .swipeLeftAndRight( + onLeft = { + slideDirection.value = ArticleSwipeDirection.Left.raw + readingViewModel.trySwitchArticle(readingProgressState.readingPrev) + }, + onRight = { + slideDirection.value = ArticleSwipeDirection.Right.raw + readingViewModel.trySwitchArticle(readingProgressState.readingNext) + } + ) + .swipeableUpDown( + onUp = { + if (!readingUiState.isFullContent) { + readingViewModel.renderFullContent() + } + }, + onDown = { + if (!readingUiState.isFullContent) { + readingViewModel.renderFullContent() + } + })) { // Top Bar TopBar( navController = navController, @@ -81,6 +112,13 @@ fun ReadingPage( AnimatedContent( targetState = readingUiState.content ?: "", transitionSpec = { + when (slideDirection.value) { + ArticleSwipeDirection.Left.raw, ArticleSwipeDirection.Right.raw -> { + val symbol = if (slideDirection.value == ArticleSwipeDirection.Right.raw) 1 else -1 + slideInHorizontally { width -> symbol * width } + fadeIn() with + slideOutHorizontally { width -> symbol * (-width) } + fadeOut() + } + ArticleSwipeDirection.Down.raw -> { slideInVertically( spring( dampingRatio = Spring.DampingRatioNoBouncy, @@ -92,6 +130,12 @@ fun ReadingPage( stiffness = Spring.StiffnessLow, ) ) + } + else -> { + fadeIn() with fadeOut() + } + } + } ) { target -> Content( @@ -120,15 +164,11 @@ fun ReadingPage( onStarred = { readingViewModel.markStarred(it) }, - onNextArticle = { - if (readingUiState.nextArticleId.isNotEmpty()) { - readingViewModel.initData(readingUiState.nextArticleId) - } - }, onFullContent = { if (it) readingViewModel.renderFullContent() else readingViewModel.renderDescriptionContent() }, + progress = "${readingProgressState.readingCurrentNumber+1} / ${readingProgressState.readingList.size}", ) } } From a92df2046ffe15a1016e421bfd976367177854e1 Mon Sep 17 00:00:00 2001 From: zhusuner <80241679+zhusuner@users.noreply.github.com> Date: Fri, 12 Jan 2024 19:18:43 +0800 Subject: [PATCH 12/12] Update ReadingViewModel.kt --- .../ui/page/home/reading/ReadingViewModel.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) 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 c5b920571..3e80c04a1 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 @@ -16,6 +16,7 @@ import me.ash.reader.domain.model.article.ArticleFlowItem import me.ash.reader.domain.model.article.ArticleWithFeed import me.ash.reader.infrastructure.rss.RssHelper import me.ash.reader.domain.service.RssService +import me.ash.reader.ui.page.home.HomeViewModel import javax.inject.Inject @HiltViewModel @@ -27,6 +28,14 @@ class ReadingViewModel @Inject constructor( private val _readingUiState = MutableStateFlow(ReadingUiState()) val readingUiState: StateFlow = _readingUiState.asStateFlow() + fun trySwitchArticle(articleId: String) { + if (articleId.isNotEmpty()) { + viewModelScope.launch { + initData(articleId) + } + } + } + fun initData(articleId: String) { showLoading() viewModelScope.launch { @@ -140,6 +149,7 @@ class ReadingViewModel @Inject constructor( val cur = _readingUiState.value.articleWithFeed?.article if (cur != null) { var found = false + var index = 0 for (item in pagingItems) { if (item is ArticleFlowItem.Article) { val itemId = item.articleWithFeed.article.id @@ -155,7 +165,38 @@ class ReadingViewModel @Inject constructor( break } } + index++ + } + } + } + } + + fun recordCurrentReadingState(homeViewModel: HomeViewModel) { + val curId = readingUiState.value.articleWithFeed?.article?.id + val list = homeViewModel.readingProgressState.value.readingList + if (curId != null && list.isNotEmpty()) { + var previous = "" + var next = "" + var targetIndex = -1 + var matched = false + + var last = "" + for (id in list) { + if (id == curId) { + previous = last + matched = true + } else if (last == curId) { + next = id + break + } + + targetIndex++ + last = id } + if (matched) { + homeViewModel.recordReadingState(targetIndex, previous, curId, next) + } else { + homeViewModel.recordReadingState(-2, "", curId, "") } } }