Horis 2 лет назад
Родитель
Сommit
549de44f51

+ 46 - 3
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.Room
 import androidx.room.RoomDatabase
 import androidx.room.RoomDatabase
 import androidx.sqlite.db.SupportSQLiteDatabase
 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 io.legado.app.help.DefaultData
 import org.intellij.lang.annotations.Language
 import org.intellij.lang.annotations.Language
 import splitties.init.appCtx
 import splitties.init.appCtx
-import java.util.*
+import java.util.Locale
 
 
 val appDb by lazy {
 val appDb by lazy {
     Room.databaseBuilder(appCtx, AppDatabase::class.java, AppDatabase.DATABASE_NAME)
     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 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() {
         val dbCallback = object : Callback() {
 
 
             override fun onCreate(db: SupportSQLiteDatabase) {
             override fun onCreate(db: SupportSQLiteDatabase) {

+ 8 - 1
app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt

@@ -88,7 +88,7 @@ interface RssSourceDao {
     fun flowGroupsUnProcessed(): Flow<List<String>>
     fun flowGroupsUnProcessed(): Flow<List<String>>
 
 
     @Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> '' and enabled = 1")
     @Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> '' and enabled = 1")
-    fun flowGroupEnabled(): Flow<List<String>>
+    fun flowEnabledGroupsUnProcessed(): Flow<List<String>>
 
 
     @get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
     @get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
     val allGroupsUnProcessed: List<String>
     val allGroupsUnProcessed: List<String>
@@ -142,4 +142,11 @@ interface RssSourceDao {
             dealGroups(list)
             dealGroups(list)
         }.flowOn(IO)
         }.flowOn(IO)
     }
     }
+
+    fun flowEnabledGroups(): Flow<List<String>> {
+        return flowEnabledGroupsUnProcessed().map { list ->
+            dealGroups(list)
+        }.flowOn(IO)
+    }
+
 }
 }

+ 7 - 2
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.base.BaseFragment
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.EventBus
+import io.legado.app.data.AppDatabase
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookGroup
 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.book.read.ReadBookActivity
 import io.legado.app.ui.main.MainViewModel
 import io.legado.app.ui.main.MainViewModel
 import io.legado.app.utils.cnCompare
 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.observeEvent
 import io.legado.app.utils.setEdgeEffectColor
 import io.legado.app.utils.setEdgeEffectColor
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.startActivity
@@ -175,7 +176,11 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
 
 
                     else -> list.sortedByDescending { it.durChapterTime }
                     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)
                 AppLog.put("书架更新出错", it)
             }.conflate().flowOn(Dispatchers.Default).collect { list ->
             }.conflate().flowOn(Dispatchers.Default).collect { list ->
                 binding.tvEmptyMsg.isGone = list.isNotEmpty()
                 binding.tvEmptyMsg.isGone = list.isNotEmpty()

+ 20 - 7
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.core.view.isGone
 import androidx.fragment.app.viewModels
 import androidx.fragment.app.viewModels
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.flowWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.VMBaseFragment
 import io.legado.app.base.VMBaseFragment
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
+import io.legado.app.data.AppDatabase
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.databinding.FragmentExploreBinding
 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.ui.main.MainFragmentInterface
 import io.legado.app.utils.applyTint
 import io.legado.app.utils.applyTint
 import io.legado.app.utils.cnCompare
 import io.legado.app.utils.cnCompare
+import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
 import io.legado.app.utils.setEdgeEffectColor
 import io.legado.app.utils.setEdgeEffectColor
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
@@ -126,11 +127,19 @@ class ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_exp
 
 
     private fun initGroupData() {
     private fun initGroupData() {
         viewLifecycleOwner.lifecycleScope.launch {
         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<ExploreViewModel>(R.layout.fragment_exp
                 else -> {
                 else -> {
                     appDb.bookSourceDao.flowExplore(searchKey)
                     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)
                 AppLog.put("发现界面更新数据出错", it)
             }.conflate().flowOn(IO).collect {
             }.conflate().flowOn(IO).collect {
                 binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty()
                 binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty()

+ 18 - 13
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.appcompat.widget.SearchView
 import androidx.fragment.app.viewModels
 import androidx.fragment.app.viewModels
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.flowWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.lifecycleScope
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.VMBaseFragment
 import io.legado.app.base.VMBaseFragment
 import io.legado.app.constant.AppLog
 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.appDb
 import io.legado.app.data.entities.RssSource
 import io.legado.app.data.entities.RssSource
 import io.legado.app.databinding.FragmentRssBinding
 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.ui.rss.subscription.RuleSubActivity
 import io.legado.app.utils.applyTint
 import io.legado.app.utils.applyTint
 import io.legado.app.utils.cnCompare
 import io.legado.app.utils.cnCompare
+import io.legado.app.utils.flowWithLifecycleAndDatabaseChange
 import io.legado.app.utils.openUrl
 import io.legado.app.utils.openUrl
 import io.legado.app.utils.setEdgeEffectColor
 import io.legado.app.utils.setEdgeEffectColor
-import io.legado.app.utils.splitNotBlank
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.flowOn
@@ -145,16 +145,17 @@ class RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),
     private fun initGroupData() {
     private fun initGroupData() {
         groupsFlowJob?.cancel()
         groupsFlowJob?.cancel()
         groupsFlowJob = viewLifecycleOwner.lifecycleScope.launch {
         groupsFlowJob = viewLifecycleOwner.lifecycleScope.launch {
-            appDb.rssSourceDao.flowGroupEnabled().catch {
+            appDb.rssSourceDao.flowEnabledGroups().catch {
                 AppLog.put("订阅界面获取分组数据失败\n${it.localizedMessage}", it)
                 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<RssViewModel>(R.layout.fragment_rss),
                 }
                 }
 
 
                 else -> appDb.rssSourceDao.flowEnabled(searchKey)
                 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)
                 AppLog.put("订阅界面更新数据出错", it)
             }.flowOn(IO).collect {
             }.flowOn(IO).collect {
                 adapter.setItems(it)
                 adapter.setItems(it)

+ 41 - 0
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.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import androidx.room.invalidationTrackerFlow
+import io.legado.app.data.appDb
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.async
 import kotlinx.coroutines.async
 import kotlinx.coroutines.ensureActive
 import kotlinx.coroutines.ensureActive
@@ -10,12 +12,14 @@ import kotlinx.coroutines.flow.FlowCollector
 import kotlinx.coroutines.flow.buffer
 import kotlinx.coroutines.flow.buffer
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.channelFlow
 import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapMerge
 import kotlinx.coroutines.flow.flatMapMerge
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.produceIn
 import kotlinx.coroutines.sync.Semaphore
 import kotlinx.coroutines.sync.Semaphore
 import kotlin.coroutines.coroutineContext
 import kotlin.coroutines.coroutineContext
 
 
@@ -204,3 +208,40 @@ fun <T> Flow<T>.flowWithLifecycleFirst(
     }
     }
     close()
     close()
 }
 }
+
+fun <T> Flow<T>.flowWithLifecycleAndDatabaseChange(
+    lifecycle: Lifecycle,
+    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
+    table: String
+): Flow<T> = callbackFlow {
+    val channel = appDb.invalidationTrackerFlow(table)
+        .conflate()
+        .produceIn(this)
+    lifecycle.repeatOnLifecycle(minActiveState) {
+        channel.receive()
+        this@flowWithLifecycleAndDatabaseChange.collect {
+            send(it)
+        }
+    }
+    close()
+}
+
+fun <T> Flow<T>.flowWithLifecycleAndDatabaseChangeFirst(
+    lifecycle: Lifecycle,
+    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
+    table: String
+): Flow<T> = 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()
+}