Skip to content

Commit

Permalink
Fix mark-all-read scroll bug (#572)
Browse files Browse the repository at this point in the history
* Fix mark-read-on-scroll bug

* Fix composition bug when adding a feed

* Don't update articles since when marking all read
  • Loading branch information
jocmp authored Dec 3, 2024
1 parent 0175c2d commit 9ef30f0
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 130 deletions.
16 changes: 5 additions & 11 deletions app/src/main/java/com/capyreader/app/ui/articles/AddFeedDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,24 @@ package com.capyreader.app.ui.articles

import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.window.Dialog
import kotlinx.coroutines.launch
import org.koin.compose.koinInject

@Composable
fun AddFeedDialog(
viewModel: AddFeedStateHolder = koinInject(),
viewModel: AddFeedViewModel = koinInject(),
onCancel: () -> Unit,
onComplete: (feedID: String) -> Unit,
) {
val scope = rememberCoroutineScope()

Dialog(onDismissRequest = onCancel) {
Column {
AddFeedView(
feedChoices = viewModel.feedChoices,
onAddFeed = { url ->
scope.launch {
viewModel.addFeed(
url = url,
onComplete = { onComplete(it.id) },
)
}
viewModel.addFeed(
url = url,
onComplete = { onComplete(it.id) },
)
},
onCancel = onCancel,
loading = viewModel.loading,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package com.capyreader.app.ui.articles

import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.jocmp.capy.Account
import com.jocmp.capy.Feed
import com.jocmp.capy.accounts.AddFeedResult
import com.jocmp.capy.accounts.FeedOption
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.jocmp.capy.common.launchIO

class AddFeedStateHolder(
class AddFeedViewModel(
val account: Account
) {
): ViewModel() {
private val _result = mutableStateOf<AddFeedResult?>(null)
private val _loading = mutableStateOf(false)

Expand All @@ -30,11 +31,11 @@ class AddFeedStateHolder(
} ?: emptyList()
}

suspend fun addFeed(
fun addFeed(
url: String,
onComplete: (feed: Feed) -> Unit,
) {
withContext(Dispatchers.IO) {
viewModelScope.launchIO {
_loading.value = true

val result = account.addFeed(url = url)
Expand Down
136 changes: 52 additions & 84 deletions app/src/main/java/com/capyreader/app/ui/articles/ArticleLayout.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
Expand Down Expand Up @@ -40,9 +39,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.LazyPagingItems
import com.capyreader.app.R
import com.capyreader.app.common.Media
import com.capyreader.app.common.Saver
Expand All @@ -55,8 +52,6 @@ import com.capyreader.app.ui.articles.list.FeedListTopBar
import com.capyreader.app.ui.articles.media.ArticleMediaView
import com.capyreader.app.ui.components.ArticleSearch
import com.capyreader.app.ui.components.rememberWebViewState
import com.capyreader.app.ui.fixtures.FeedSample
import com.capyreader.app.ui.fixtures.FolderPreviewFixture
import com.capyreader.app.ui.isCompact
import com.jocmp.capy.Article
import com.jocmp.capy.ArticleFilter
Expand All @@ -65,10 +60,7 @@ import com.jocmp.capy.Feed
import com.jocmp.capy.Folder
import com.jocmp.capy.MarkRead
import com.jocmp.capy.common.launchUI
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch

@OptIn(
Expand All @@ -82,7 +74,7 @@ fun ArticleLayout(
feeds: List<Feed>,
allFeeds: List<Feed>,
allFolders: List<Folder>,
articles: Flow<PagingData<Article>>,
pagingArticles: LazyPagingItems<Article>,
article: Article?,
search: ArticleSearch,
statusCount: Long,
Expand Down Expand Up @@ -115,8 +107,7 @@ fun ArticleLayout(
val coroutineScope = rememberCoroutineScope()
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator()
var isRefreshing by remember { mutableStateOf(false) }
val pagingArticles = articles.collectAsLazyPagingItems(Dispatchers.IO)
val listState = rememberArticleListState(filter)
val listState = rememberLazyListState()
val snackbarHost = remember { SnackbarHostState() }
val addFeedSuccessMessage = stringResource(R.string.add_feed_success)
val currentFeed = findCurrentFeed(filter, allFeeds)
Expand All @@ -127,6 +118,7 @@ fun ArticleLayout(
onUnauthorizedDismissRequest()
setUpdatePasswordDialogOpen(true)
}
var listVisible by remember { mutableStateOf(true) }

suspend fun navigateToDetail() {
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
Expand Down Expand Up @@ -160,7 +152,6 @@ fun ArticleLayout(

val refreshPagination = {
coroutineScope.launch {
pagingArticles.refresh()
resetScrollBehaviorOffset()
}
}
Expand All @@ -177,11 +168,26 @@ fun ArticleLayout(
}
}

val openNextList = suspend {
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.List)
suspend fun openNextStatus(action: suspend () -> Unit) {
listVisible = false
delay(200)
drawerState.close()
refreshPagination()
action()
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.List)
resetScrollBehaviorOffset()

coroutineScope.launch {
delay(500)
if (!listVisible) {
listVisible = true
}
}
}

fun openNextList(action: suspend () -> Unit) {
coroutineScope.launchUI {
openNextStatus(action)
drawerState.close()
}
}

val toggleDrawer = {
Expand Down Expand Up @@ -212,8 +218,7 @@ fun ArticleLayout(

val onFeedAdded = { feedID: String ->
coroutineScope.launch {
onSelectFeed(feedID)
openNextList()
openNextList { onSelectFeed(feedID) }

showSnackbar(addFeedSuccessMessage)
}
Expand Down Expand Up @@ -242,19 +247,15 @@ fun ArticleLayout(
feeds = feeds,
onSelectFolder = {
if (!filter.isFolderSelect(it)) {
onSelectFolder(it.title)
coroutineScope.launch {
openNextList()
}
openNextList { onSelectFolder(it.title) }
} else {
closeDrawer()
}
},
onSelectFeed = {
coroutineScope.launch {
if (!filter.isFeedSelected(it)) {
onSelectFeed(it.id)
openNextList()
openNextList { onSelectFeed(it.id) }
} else {
closeDrawer()
}
Expand All @@ -264,19 +265,17 @@ fun ArticleLayout(
onNavigateToSettings = onNavigateToSettings,
onFilterSelect = {
if (!filter.hasArticlesSelected()) {
onSelectArticleFilter()
coroutineScope.launch {
openNextList()
}
openNextList { onSelectArticleFilter() }
} else {
closeDrawer()
}
},
filter = filter,
statusCount = statusCount,
onSelectStatus = {
onSelectStatus(it)
refreshPagination()
coroutineScope.launchUI {
openNextStatus { onSelectStatus(it) }
}
}
)
},
Expand Down Expand Up @@ -341,13 +340,20 @@ fun ArticleLayout(
)
}
} else {
ArticleList(
articles = pagingArticles,
selectedArticleKey = article?.id,
listState = listState,
onMarkAllRead = onMarkAllRead,
onSelect = { selectArticle(it) },
)
AnimatedVisibility(
listVisible,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier.fillMaxSize(),
) {
ArticleList(
articles = pagingArticles,
selectedArticleKey = article?.id,
listState = listState,
onMarkAllRead = onMarkAllRead,
onSelect = { selectArticle(it) },
)
}
}
}
}
Expand Down Expand Up @@ -446,6 +452,13 @@ fun ArticleLayout(
) {
toggleDrawer()
}

LaunchedEffect(pagingArticles.itemCount) {
if (!listVisible) {
listState.scrollToItem(0)
listVisible = true
}
}
}

fun isFeedActive(
Expand All @@ -466,14 +479,6 @@ fun findCurrentFeed(filter: ArticleFilter, feeds: List<Feed>): Feed? {
return null
}


@Composable
fun rememberArticleListState(filter: ArticleFilter): LazyListState {
return rememberSaveable(filter, saver = LazyListState.Saver) {
LazyListState()
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun rememberArticleTopBar(filter: ArticleFilter): TopAppBarScrollBehavior {
Expand All @@ -487,40 +492,3 @@ fun rememberArticleTopBar(filter: ArticleFilter): TopAppBarScrollBehavior {

return pinnedScrollBehavior(state)
}

@Preview
@Composable
fun ArticleLayoutPreview() {
val folders = FolderPreviewFixture().values.take(2).toList()
val feeds = FeedSample().values.take(2).toList()

MaterialTheme {
ArticleLayout(
filter = ArticleFilter.default(),
folders = folders,
feeds = feeds,
allFolders = emptyList(),
allFeeds = emptyList(),
articles = emptyFlow(),
search = ArticleSearch(),
article = null,
statusCount = 30,
refreshInterval = RefreshInterval.MANUALLY_ONLY,
onFeedRefresh = {},
onSelectFolder = {},
onSelectFeed = {},
onSelectArticleFilter = { },
onSelectStatus = {},
onSelectArticle = {},
onNavigateToSettings = { },
onRequestClearArticle = { },
onToggleArticleRead = { },
onToggleArticleStar = { },
onMarkAllRead = { },
onRemoveFeed = { _, _, _ -> },
drawerValue = DrawerValue.Open,
showUnauthorizedMessage = false,
onUnauthorizedDismissRequest = {},
)
}
}
43 changes: 20 additions & 23 deletions app/src/main/java/com/capyreader/app/ui/articles/ArticleList.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
Expand Down Expand Up @@ -45,28 +44,26 @@ fun ArticleList(
}
}

key(articles.itemCount) {
LazyScrollbar(state = listState) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
) {
items(count = articles.itemCount) { index ->
val item = articles[index]

Box {
if (item == null) {
PlaceholderArticleRow(articleOptions.imagePreview)
} else {
ArticleRow(
article = item,
selected = selectedArticleKey == item.id,
onSelect = { selectArticle(it) },
onMarkAllRead = onMarkAllRead,
currentTime = currentTime,
options = articleOptions
)
}
LazyScrollbar(state = listState) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
) {
items(count = articles.itemCount) { index ->
val item = articles[index]

Box {
if (item == null) {
PlaceholderArticleRow(articleOptions.imagePreview)
} else {
ArticleRow(
article = item,
selected = selectedArticleKey == item.id,
onSelect = { selectArticle(it) },
onMarkAllRead = onMarkAllRead,
currentTime = currentTime,
options = articleOptions
)
}
}
}
Expand Down
Loading

0 comments on commit 9ef30f0

Please sign in to comment.