Optimize feed loading and chunk sync
This commit is contained in:
parent
1384012c44
commit
9a933ce486
|
@ -7,7 +7,10 @@ import androidx.compose.material.icons.rounded.Star
|
|||
import androidx.compose.material.icons.rounded.StarOutline
|
||||
import androidx.compose.material.icons.rounded.Subject
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import me.ash.reader.R
|
||||
|
||||
|
@ -40,9 +43,19 @@ class Filter(
|
|||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
@Composable
|
||||
fun Filter.getName(): String = when (this) {
|
||||
Filter.Unread -> stringResource(R.string.unread)
|
||||
Filter.Starred -> stringResource(R.string.starred)
|
||||
else -> stringResource(R.string.all)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Stable
|
||||
@Composable
|
||||
fun Filter.getDesc(important: Int): String = when (this) {
|
||||
Filter.Starred -> pluralStringResource(R.plurals.starred_desc, important, important)
|
||||
Filter.Unread -> pluralStringResource(R.plurals.unread_desc, important, important)
|
||||
else -> pluralStringResource(R.plurals.all_desc, important, important)
|
||||
}
|
|
@ -27,6 +27,7 @@ abstract class AbstractRssRepository constructor(
|
|||
private val feedDao: FeedDao,
|
||||
private val workManager: WorkManager,
|
||||
private val dispatcherIO: CoroutineDispatcher,
|
||||
private val dispatcherDefault: CoroutineDispatcher,
|
||||
) {
|
||||
abstract suspend fun updateArticleInfo(article: Article)
|
||||
|
||||
|
@ -113,11 +114,18 @@ abstract class AbstractRssRepository constructor(
|
|||
else -> articleDao.queryImportantCountWhenIsAll(accountId)
|
||||
}.mapLatest {
|
||||
mapOf(
|
||||
// Groups
|
||||
*(it.groupBy { it.groupId }.map {
|
||||
it.key to it.value.sumOf { it.important }
|
||||
}.toTypedArray()),
|
||||
// Feeds
|
||||
*(it.map {
|
||||
it.feedId to it.important
|
||||
}.toTypedArray())
|
||||
}.toTypedArray()),
|
||||
// All summary
|
||||
"sum" to it.sumOf { it.important }
|
||||
)
|
||||
}.flowOn(dispatcherIO)
|
||||
}.flowOn(dispatcherDefault)
|
||||
}
|
||||
|
||||
suspend fun findFeedById(id: String): Feed? {
|
||||
|
|
|
@ -9,7 +9,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import me.ash.reader.data.dao.AccountDao
|
||||
import me.ash.reader.data.dao.ArticleDao
|
||||
import me.ash.reader.data.dao.FeedDao
|
||||
|
@ -35,14 +35,14 @@ class LocalRssRepository @Inject constructor(
|
|||
private val notificationHelper: NotificationHelper,
|
||||
private val accountDao: AccountDao,
|
||||
private val groupDao: GroupDao,
|
||||
@DispatcherDefault
|
||||
private val dispatcherDefault: CoroutineDispatcher,
|
||||
@DispatcherIO
|
||||
private val dispatcherIO: CoroutineDispatcher,
|
||||
@DispatcherDefault
|
||||
private val dispatcherDefault: CoroutineDispatcher,
|
||||
workManager: WorkManager,
|
||||
) : AbstractRssRepository(
|
||||
context, accountDao, articleDao, groupDao,
|
||||
feedDao, workManager, dispatcherIO
|
||||
feedDao, workManager, dispatcherIO, dispatcherDefault
|
||||
) {
|
||||
|
||||
override suspend fun updateArticleInfo(article: Article) {
|
||||
|
@ -71,25 +71,29 @@ class LocalRssRepository @Inject constructor(
|
|||
}
|
||||
|
||||
override suspend fun sync(coroutineWorker: CoroutineWorker): ListenableWorker.Result {
|
||||
return withContext(dispatcherDefault) {
|
||||
return supervisorScope {
|
||||
val preTime = System.currentTimeMillis()
|
||||
val accountId = context.currentAccountId
|
||||
feedDao.queryAll(accountId)
|
||||
.also { coroutineWorker.setProgress(setIsSyncing(true)) }
|
||||
.map { feed -> async { syncFeed(feed) } }
|
||||
.awaitAll()
|
||||
.chunked(16)
|
||||
.forEach {
|
||||
if (it.isNotify) {
|
||||
notificationHelper.notify(
|
||||
FeedWithArticle(
|
||||
it.feedWithArticle.feed,
|
||||
it.map { feed -> async { syncFeed(feed) } }
|
||||
.awaitAll()
|
||||
.forEach {
|
||||
if (it.isNotify) {
|
||||
notificationHelper.notify(
|
||||
FeedWithArticle(
|
||||
it.feedWithArticle.feed,
|
||||
articleDao.insertListIfNotExist(it.feedWithArticle.articles)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
articleDao.insertListIfNotExist(it.feedWithArticle.articles)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
articleDao.insertListIfNotExist(it.feedWithArticle.articles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.i("RlOG", "onCompletion: ${System.currentTimeMillis() - preTime}")
|
||||
accountDao.queryById(accountId)?.let { account ->
|
||||
accountDao.update(
|
||||
|
|
|
@ -80,10 +80,12 @@ class RssHelper @Inject constructor(
|
|||
feed: Feed,
|
||||
latestLink: String? = null,
|
||||
): List<Article> {
|
||||
return withContext(dispatcherIO) {
|
||||
val accountId = context.currentAccountId
|
||||
val syndFeed = SyndFeedInput().build(XmlReader(inputStream(okHttpClient, feed.url)))
|
||||
syndFeed.entries.asSequence()
|
||||
val accountId = context.currentAccountId
|
||||
return inputStream(okHttpClient, feed.url).use {
|
||||
SyndFeedInput().apply { isPreserveWireFeed = true }
|
||||
.build(XmlReader(it))
|
||||
.entries
|
||||
.asSequence()
|
||||
.takeWhile { latestLink == null || latestLink != it.link }
|
||||
.map { article(feed, accountId, it) }
|
||||
.toList()
|
||||
|
|
|
@ -6,6 +6,8 @@ import androidx.hilt.work.HiltWorker
|
|||
import androidx.work.*
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -18,7 +20,9 @@ class SyncWorker @AssistedInject constructor(
|
|||
|
||||
override suspend fun doWork(): Result {
|
||||
Log.i("RLog", "doWork: ")
|
||||
return rssRepository.get().sync(this)
|
||||
return withContext(Dispatchers.Default) {
|
||||
rssRepository.get().sync(this@SyncWorker)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -65,6 +65,10 @@ fun FeedsPage(
|
|||
|
||||
val feedsUiState = feedsViewModel.feedsUiState.collectAsStateValue()
|
||||
val filterUiState = homeViewModel.filterUiState.collectAsStateValue()
|
||||
val importantSum =
|
||||
feedsUiState.importantSum.collectAsStateValue(initial = stringResource(R.string.loading))
|
||||
val groupWithFeedList =
|
||||
feedsUiState.groupWithFeedList.collectAsStateValue(initial = emptyList())
|
||||
|
||||
val newVersion = LocalNewVersionNumber.current
|
||||
val skipVersion = LocalSkipVersionNumber.current
|
||||
|
@ -107,9 +111,9 @@ fun FeedsPage(
|
|||
}
|
||||
}
|
||||
|
||||
val groupsVisible = remember(feedsUiState.groupWithFeedList) {
|
||||
val groupsVisible = remember(groupWithFeedList) {
|
||||
mutableStateMapOf(
|
||||
*(feedsUiState.groupWithFeedList.filterIsInstance<GroupFeedsView.Group>().map {
|
||||
*(groupWithFeedList.filterIsInstance<GroupFeedsView.Group>().map {
|
||||
it.group.id to groupListExpand.value
|
||||
}.toTypedArray())
|
||||
)
|
||||
|
@ -121,7 +125,7 @@ fun FeedsPage(
|
|||
|
||||
LaunchedEffect(filterUiState) {
|
||||
snapshotFlow { filterUiState }.collect {
|
||||
feedsViewModel.fetchData(it)
|
||||
feedsViewModel.pullFeeds(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,7 +184,7 @@ fun FeedsPage(
|
|||
item {
|
||||
Banner(
|
||||
title = filterUiState.filter.getName(),
|
||||
desc = feedsUiState.importantSum.ifEmpty { stringResource(R.string.loading) },
|
||||
desc = importantSum,
|
||||
icon = filterUiState.filter.iconOutline,
|
||||
action = {
|
||||
Icon(
|
||||
|
@ -207,7 +211,7 @@ fun FeedsPage(
|
|||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
itemsIndexed(feedsUiState.groupWithFeedList) { index, groupWithFeed ->
|
||||
itemsIndexed(groupWithFeedList) { index, groupWithFeed ->
|
||||
when (groupWithFeed) {
|
||||
is GroupFeedsView.Group -> {
|
||||
if (index != 0) {
|
||||
|
@ -218,7 +222,7 @@ fun FeedsPage(
|
|||
group = groupWithFeed.group,
|
||||
alpha = groupAlpha,
|
||||
indicatorAlpha = groupIndicatorAlpha,
|
||||
isEnded = { index == feedsUiState.groupWithFeedList.lastIndex },
|
||||
isEnded = { index == groupWithFeedList.lastIndex },
|
||||
onExpanded = {
|
||||
groupsVisible[groupWithFeed.group.id] =
|
||||
!(groupsVisible[groupWithFeed.group.id] ?: false)
|
||||
|
@ -239,7 +243,7 @@ fun FeedsPage(
|
|||
feed = groupWithFeed.feed,
|
||||
alpha = groupAlpha,
|
||||
badgeAlpha = feedBadgeAlpha,
|
||||
isEnded = { index == feedsUiState.groupWithFeedList.lastIndex || feedsUiState.groupWithFeedList[index + 1] is GroupFeedsView.Group },
|
||||
isEnded = { index == groupWithFeedList.lastIndex || groupWithFeedList[index + 1] is GroupFeedsView.Group },
|
||||
isExpanded = { groupsVisible[groupWithFeed.feed.groupId] ?: false },
|
||||
) {
|
||||
filterChange(
|
||||
|
|
|
@ -2,7 +2,6 @@ package me.ash.reader.ui.page.home.feeds
|
|||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
@ -54,52 +53,52 @@ class FeedsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun fetchData(filterState: FilterState) {
|
||||
viewModelScope.launch(dispatcherIO) {
|
||||
pullFeeds(
|
||||
isStarred = filterState.filter.isStarred(),
|
||||
isUnread = filterState.filter.isUnread(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun pullFeeds(isStarred: Boolean, isUnread: Boolean) {
|
||||
combine(
|
||||
rssRepository.get().pullFeeds(),
|
||||
rssRepository.get().pullImportant(isStarred, isUnread),
|
||||
) { groupWithFeedList, importantMap ->
|
||||
groupWithFeedList.fastForEach {
|
||||
var groupImportant = 0
|
||||
it.feeds.fastForEach {
|
||||
it.important = importantMap[it.id]
|
||||
groupImportant += it.important ?: 0
|
||||
}
|
||||
it.group.important = groupImportant
|
||||
}
|
||||
groupWithFeedList
|
||||
}.mapLatest { groupWithFeedList ->
|
||||
_feedsUiState.update {
|
||||
it.copy(
|
||||
importantSum = groupWithFeedList.sumOf { it.group.important ?: 0 }.run {
|
||||
when {
|
||||
isStarred -> stringsRepository.getQuantityString(
|
||||
R.plurals.starred_desc,
|
||||
this,
|
||||
this
|
||||
)
|
||||
isUnread -> stringsRepository.getQuantityString(
|
||||
R.plurals.unread_desc,
|
||||
this,
|
||||
this
|
||||
)
|
||||
else -> stringsRepository.getQuantityString(
|
||||
R.plurals.all_desc,
|
||||
fun pullFeeds(filterState: FilterState) {
|
||||
val isStarred = filterState.filter.isStarred()
|
||||
val isUnread = filterState.filter.isUnread()
|
||||
_feedsUiState.update {
|
||||
it.copy(
|
||||
importantSum = rssRepository.get().pullImportant(isStarred, isUnread)
|
||||
.mapLatest {
|
||||
(it["sum"] ?: 0).run {
|
||||
stringsRepository.getQuantityString(
|
||||
when {
|
||||
isStarred -> R.plurals.starred_desc
|
||||
isUnread -> R.plurals.unread_desc
|
||||
else -> R.plurals.all_desc
|
||||
},
|
||||
this,
|
||||
this
|
||||
)
|
||||
}
|
||||
},
|
||||
groupWithFeedList = groupWithFeedList.map {
|
||||
}.flowOn(dispatcherDefault),
|
||||
groupWithFeedList = combine(
|
||||
rssRepository.get().pullImportant(isStarred, isUnread),
|
||||
rssRepository.get().pullFeeds()
|
||||
) { importantMap, groupWithFeedList ->
|
||||
val groupIterator = groupWithFeedList.iterator()
|
||||
while (groupIterator.hasNext()) {
|
||||
val groupWithFeed = groupIterator.next()
|
||||
val groupImportant = importantMap[groupWithFeed.group.id] ?: 0
|
||||
if ((isStarred || isUnread) && groupImportant == 0) {
|
||||
groupIterator.remove()
|
||||
continue
|
||||
}
|
||||
groupWithFeed.group.important = groupImportant
|
||||
val feedIterator = groupWithFeed.feeds.iterator()
|
||||
while (feedIterator.hasNext()) {
|
||||
val feed = feedIterator.next()
|
||||
val feedImportant = importantMap[feed.id] ?: 0
|
||||
if ((isStarred || isUnread) && feedImportant == 0) {
|
||||
feedIterator.remove()
|
||||
continue
|
||||
}
|
||||
feed.important = feedImportant
|
||||
}
|
||||
}
|
||||
groupWithFeedList
|
||||
}.mapLatest { groupWithFeedList ->
|
||||
groupWithFeedList.map {
|
||||
mutableListOf<GroupFeedsView>(GroupFeedsView.Group(it.group)).apply {
|
||||
addAll(
|
||||
it.feeds.map {
|
||||
|
@ -107,19 +106,17 @@ class FeedsViewModel @Inject constructor(
|
|||
}
|
||||
)
|
||||
}
|
||||
}.flatten(),
|
||||
)
|
||||
}
|
||||
}.catch {
|
||||
Log.e("RLog", "catch in articleRepository.pullFeeds(): ${it.message}")
|
||||
}.flowOn(dispatcherDefault).collect()
|
||||
}.flatten()
|
||||
}.flowOn(dispatcherDefault),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FeedsUiState(
|
||||
val account: Account? = null,
|
||||
val importantSum: String = "",
|
||||
val groupWithFeedList: List<GroupFeedsView> = emptyList(),
|
||||
val importantSum: Flow<String> = emptyFlow(),
|
||||
val groupWithFeedList: Flow<List<GroupFeedsView>> = emptyFlow(),
|
||||
val listState: LazyListState = LazyListState(),
|
||||
val groupsVisible: Boolean = true,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue
Block a user