From b0f76445f81c6965b7f3ab29ef124dc5853a02ff Mon Sep 17 00:00:00 2001 From: Horis <8674809+821938089@users.noreply.github.com> Date: Tue, 25 Jun 2024 23:07:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/data/AppDatabase.kt | 49 +++++++++++++++++-- .../io/legado/app/data/dao/RssSourceDao.kt | 9 +++- .../bookshelf/style1/books/BooksFragment.kt | 9 +++- .../app/ui/main/explore/ExploreFragment.kt | 27 +++++++--- .../io/legado/app/ui/main/rss/RssFragment.kt | 31 +++++++----- .../io/legado/app/utils/FlowExtensions.kt | 41 ++++++++++++++++ 6 files changed, 140 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index e8f8348c8..c5e8221d1 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -7,12 +7,51 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase import androidx.sqlite.db.SupportSQLiteDatabase -import io.legado.app.data.dao.* -import io.legado.app.data.entities.* +import io.legado.app.data.dao.BookChapterDao +import io.legado.app.data.dao.BookDao +import io.legado.app.data.dao.BookGroupDao +import io.legado.app.data.dao.BookSourceDao +import io.legado.app.data.dao.BookmarkDao +import io.legado.app.data.dao.CacheDao +import io.legado.app.data.dao.CookieDao +import io.legado.app.data.dao.DictRuleDao +import io.legado.app.data.dao.HttpTTSDao +import io.legado.app.data.dao.KeyboardAssistsDao +import io.legado.app.data.dao.ReadRecordDao +import io.legado.app.data.dao.ReplaceRuleDao +import io.legado.app.data.dao.RssArticleDao +import io.legado.app.data.dao.RssSourceDao +import io.legado.app.data.dao.RssStarDao +import io.legado.app.data.dao.RuleSubDao +import io.legado.app.data.dao.SearchBookDao +import io.legado.app.data.dao.SearchKeywordDao +import io.legado.app.data.dao.ServerDao +import io.legado.app.data.dao.TxtTocRuleDao +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter +import io.legado.app.data.entities.BookGroup +import io.legado.app.data.entities.BookSource +import io.legado.app.data.entities.Bookmark +import io.legado.app.data.entities.Cache +import io.legado.app.data.entities.Cookie +import io.legado.app.data.entities.DictRule +import io.legado.app.data.entities.HttpTTS +import io.legado.app.data.entities.KeyboardAssist +import io.legado.app.data.entities.ReadRecord +import io.legado.app.data.entities.ReplaceRule +import io.legado.app.data.entities.RssArticle +import io.legado.app.data.entities.RssReadRecord +import io.legado.app.data.entities.RssSource +import io.legado.app.data.entities.RssStar +import io.legado.app.data.entities.RuleSub +import io.legado.app.data.entities.SearchBook +import io.legado.app.data.entities.SearchKeyword +import io.legado.app.data.entities.Server +import io.legado.app.data.entities.TxtTocRule import io.legado.app.help.DefaultData import org.intellij.lang.annotations.Language import splitties.init.appCtx -import java.util.* +import java.util.Locale val appDb by lazy { Room.databaseBuilder(appCtx, AppDatabase::class.java, AppDatabase.DATABASE_NAME) @@ -89,6 +128,10 @@ abstract class AppDatabase : RoomDatabase() { const val DATABASE_NAME = "legado.db" + const val BOOK_TABLE_NAME = "books" + const val BOOK_SOURCE_TABLE_NAME = "book_sources" + const val RSS_SOURCE_TABLE_NAME = "rssSources" + val dbCallback = object : Callback() { override fun onCreate(db: SupportSQLiteDatabase) { diff --git a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt index 017e7ec8a..9846e6741 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt @@ -88,7 +88,7 @@ interface RssSourceDao { fun flowGroupsUnProcessed(): Flow> @Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> '' and enabled = 1") - fun flowGroupEnabled(): Flow> + fun flowEnabledGroupsUnProcessed(): Flow> @get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''") val allGroupsUnProcessed: List @@ -142,4 +142,11 @@ interface RssSourceDao { dealGroups(list) }.flowOn(IO) } + + fun flowEnabledGroups(): Flow> { + return flowEnabledGroupsUnProcessed().map { list -> + dealGroups(list) + }.flowOn(IO) + } + } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksFragment.kt index fe9977c72..2b779c1d6 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksFragment.kt @@ -15,6 +15,7 @@ import io.legado.app.R import io.legado.app.base.BaseFragment import io.legado.app.constant.AppLog import io.legado.app.constant.EventBus +import io.legado.app.data.AppDatabase import io.legado.app.data.appDb import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup @@ -28,7 +29,7 @@ import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.main.MainViewModel import io.legado.app.utils.cnCompare -import io.legado.app.utils.flowWithLifecycleFirst +import io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst import io.legado.app.utils.observeEvent import io.legado.app.utils.setEdgeEffectColor import io.legado.app.utils.startActivity @@ -175,7 +176,11 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books), else -> list.sortedByDescending { it.durChapterTime } } - }.flowWithLifecycleFirst(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch { + }.flowWithLifecycleAndDatabaseChangeFirst( + viewLifecycleOwner.lifecycle, + Lifecycle.State.RESUMED, + AppDatabase.BOOK_TABLE_NAME + ).catch { AppLog.put("书架更新出错", it) }.conflate().flowOn(Dispatchers.Default).collect { list -> binding.tvEmptyMsg.isGone = list.isNotEmpty() diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt index 204aff2ca..9882e0260 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt @@ -9,13 +9,13 @@ import androidx.appcompat.widget.SearchView import androidx.core.view.isGone import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.R import io.legado.app.base.VMBaseFragment import io.legado.app.constant.AppLog +import io.legado.app.data.AppDatabase import io.legado.app.data.appDb import io.legado.app.data.entities.BookSourcePart import io.legado.app.databinding.FragmentExploreBinding @@ -30,6 +30,7 @@ import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.main.MainFragmentInterface import io.legado.app.utils.applyTint import io.legado.app.utils.cnCompare +import io.legado.app.utils.flowWithLifecycleAndDatabaseChange import io.legado.app.utils.setEdgeEffectColor import io.legado.app.utils.startActivity import io.legado.app.utils.viewbindingdelegate.viewBinding @@ -126,11 +127,19 @@ class ExploreFragment() : VMBaseFragment(R.layout.fragment_exp private fun initGroupData() { viewLifecycleOwner.lifecycleScope.launch { - appDb.bookSourceDao.flowExploreGroups().conflate().collect { - groups.clear() - groups.addAll(it) - upGroupsMenu() - } + appDb.bookSourceDao.flowExploreGroups() + .flowWithLifecycleAndDatabaseChange( + viewLifecycleOwner.lifecycle, + Lifecycle.State.RESUMED, + AppDatabase.BOOK_SOURCE_TABLE_NAME + ) + .conflate() + .collect { + groups.clear() + groups.addAll(it) + upGroupsMenu() + delay(500) + } } } @@ -150,7 +159,11 @@ class ExploreFragment() : VMBaseFragment(R.layout.fragment_exp else -> { appDb.bookSourceDao.flowExplore(searchKey) } - }.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch { + }.flowWithLifecycleAndDatabaseChange( + viewLifecycleOwner.lifecycle, + Lifecycle.State.RESUMED, + AppDatabase.BOOK_SOURCE_TABLE_NAME + ).catch { AppLog.put("发现界面更新数据出错", it) }.conflate().flowOn(IO).collect { binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty() diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt index b587955fa..804bc7ff7 100644 --- a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt @@ -8,12 +8,11 @@ import android.view.View import androidx.appcompat.widget.SearchView import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import io.legado.app.R import io.legado.app.base.VMBaseFragment import io.legado.app.constant.AppLog -import io.legado.app.constant.AppPattern +import io.legado.app.data.AppDatabase import io.legado.app.data.appDb import io.legado.app.data.entities.RssSource import io.legado.app.databinding.FragmentRssBinding @@ -30,13 +29,14 @@ import io.legado.app.ui.rss.source.manage.RssSourceActivity import io.legado.app.ui.rss.subscription.RuleSubActivity import io.legado.app.utils.applyTint import io.legado.app.utils.cnCompare +import io.legado.app.utils.flowWithLifecycleAndDatabaseChange import io.legado.app.utils.openUrl import io.legado.app.utils.setEdgeEffectColor -import io.legado.app.utils.splitNotBlank import io.legado.app.utils.startActivity import io.legado.app.utils.viewbindingdelegate.viewBinding import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn @@ -145,16 +145,17 @@ class RssFragment() : VMBaseFragment(R.layout.fragment_rss), private fun initGroupData() { groupsFlowJob?.cancel() groupsFlowJob = viewLifecycleOwner.lifecycleScope.launch { - appDb.rssSourceDao.flowGroupEnabled().catch { + appDb.rssSourceDao.flowEnabledGroups().catch { AppLog.put("订阅界面获取分组数据失败\n${it.localizedMessage}", it) - }.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED) - .flowOn(IO).conflate().collect { - groups.clear() - it.map { group -> - groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) - } - upGroupsMenu() - } + }.flowWithLifecycleAndDatabaseChange( + viewLifecycleOwner.lifecycle, + Lifecycle.State.RESUMED, + AppDatabase.RSS_SOURCE_TABLE_NAME + ).conflate().collect { + groups.clear() + groups.addAll(it) + upGroupsMenu() + } } } @@ -169,7 +170,11 @@ class RssFragment() : VMBaseFragment(R.layout.fragment_rss), } else -> appDb.rssSourceDao.flowEnabled(searchKey) - }.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED).catch { + }.flowWithLifecycleAndDatabaseChange( + viewLifecycleOwner.lifecycle, + Lifecycle.State.RESUMED, + AppDatabase.RSS_SOURCE_TABLE_NAME + ).catch { AppLog.put("订阅界面更新数据出错", it) }.flowOn(IO).collect { adapter.setItems(it) diff --git a/app/src/main/java/io/legado/app/utils/FlowExtensions.kt b/app/src/main/java/io/legado/app/utils/FlowExtensions.kt index 53f12a4b8..c08805906 100644 --- a/app/src/main/java/io/legado/app/utils/FlowExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/FlowExtensions.kt @@ -2,6 +2,8 @@ package io.legado.app.utils import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import androidx.room.invalidationTrackerFlow +import io.legado.app.data.appDb import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.ensureActive @@ -10,12 +12,14 @@ import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.produceIn import kotlinx.coroutines.sync.Semaphore import kotlin.coroutines.coroutineContext @@ -204,3 +208,40 @@ fun Flow.flowWithLifecycleFirst( } close() } + +fun Flow.flowWithLifecycleAndDatabaseChange( + lifecycle: Lifecycle, + minActiveState: Lifecycle.State = Lifecycle.State.STARTED, + table: String +): Flow = callbackFlow { + val channel = appDb.invalidationTrackerFlow(table) + .conflate() + .produceIn(this) + lifecycle.repeatOnLifecycle(minActiveState) { + channel.receive() + this@flowWithLifecycleAndDatabaseChange.collect { + send(it) + } + } + close() +} + +fun Flow.flowWithLifecycleAndDatabaseChangeFirst( + lifecycle: Lifecycle, + minActiveState: Lifecycle.State = Lifecycle.State.STARTED, + table: String +): Flow = callbackFlow { + if (!lifecycle.currentState.isAtLeast(minActiveState)) { + send(first()) + } + val channel = appDb.invalidationTrackerFlow(table) + .conflate() + .produceIn(this) + lifecycle.repeatOnLifecycle(minActiveState) { + channel.receive() + this@flowWithLifecycleAndDatabaseChangeFirst.collect { + send(it) + } + } + close() +}